( req: Request, opts: ServeDirOptions, )
| 694 | } |
| 695 | |
| 696 | async function createServeDirResponse( |
| 697 | req: Request, |
| 698 | opts: ServeDirOptions, |
| 699 | ) { |
| 700 | const target = opts.fsRoot ?? "."; |
| 701 | const urlRoot = opts.urlRoot; |
| 702 | const showIndex = opts.showIndex ?? true; |
| 703 | const cleanUrls = (opts as { cleanUrls?: boolean }).cleanUrls ?? false; |
| 704 | const showDotfiles = opts.showDotfiles || false; |
| 705 | const { etagAlgorithm = "SHA-256", showDirListing = false, quiet = false } = |
| 706 | opts; |
| 707 | |
| 708 | const url = new URL(req.url); |
| 709 | const decodedUrl = decodeURIComponent(url.pathname); |
| 710 | let normalizedPath = posixNormalize(decodedUrl); |
| 711 | |
| 712 | if (urlRoot && !normalizedPath.startsWith("/" + urlRoot)) { |
| 713 | return createStandardResponse(STATUS_CODE.NotFound); |
| 714 | } |
| 715 | |
| 716 | // Redirect paths like `/foo////bar` and `/foo/bar/////` to normalized paths. |
| 717 | if (normalizedPath !== decodedUrl) { |
| 718 | url.pathname = normalizedPath; |
| 719 | return Response.redirect(url, 301); |
| 720 | } |
| 721 | |
| 722 | if (urlRoot) { |
| 723 | normalizedPath = normalizedPath.replace(urlRoot, ""); |
| 724 | } |
| 725 | |
| 726 | // Remove trailing slashes to avoid ENOENT errors |
| 727 | // when accessing a path to a file with a trailing slash. |
| 728 | if (normalizedPath.endsWith("/")) { |
| 729 | normalizedPath = normalizedPath.slice(0, -1); |
| 730 | } |
| 731 | |
| 732 | // Exclude dotfiles if showDotfiles is false |
| 733 | if (!showDotfiles && /\/\./.test(normalizedPath)) { |
| 734 | return createStandardResponse(STATUS_CODE.NotFound); |
| 735 | } |
| 736 | |
| 737 | // Resolve path |
| 738 | // If cleanUrls is enabled, automatically append ".html" if not present |
| 739 | // and it does not shadow another existing file or directory |
| 740 | let fsPath = join(target, normalizedPath); |
| 741 | if (cleanUrls && !fsPath.endsWith(".html") && !(await exists(fsPath))) { |
| 742 | fsPath += ".html"; |
| 743 | } |
| 744 | const fileInfo = await Deno.stat(fsPath); |
| 745 | |
| 746 | // For files, remove the trailing slash from the path. |
| 747 | if (fileInfo.isFile && url.pathname.endsWith("/")) { |
| 748 | url.pathname = url.pathname.slice(0, -1); |
| 749 | return Response.redirect(url, 301); |
| 750 | } |
| 751 | // For directories, the path must have a trailing slash. |
| 752 | if (fileInfo.isDirectory && !url.pathname.endsWith("/")) { |
| 753 | // On directory listing pages, |
no test coverage detected