protected override async Task <FileMetadata> GetFileMetadataImplAsync(string path, FileMetadataGetFlags flags = FileMetadataGetFlags.DefaultAll, CancellationToken cancel = default)
        {
            FileMetadata physicalMetadata = await UnderlayFileSystem.GetFileMetadataAsync(path, flags, cancel);

            try
            {
                long headerOffset = 0;
                long physicalSize = physicalMetadata.Size;

                using (FileObject physicalFile = await UnderlayFileSystem.OpenAsync(path))
                {
                    byte[]        bomRead = new byte[3];
                    Memory <byte> tmp     = new byte[3];
                    if (await physicalFile.ReadRandomAsync(0, tmp, cancel) == tmp.Length)
                    {
                        if (tmp.Span.SequenceEqual(Str.BOM_UTF_8.Span))
                        {
                            headerOffset = 3;
                        }
                    }

                    physicalSize = await physicalFile.GetFileSizeAsync(true, cancel) - headerOffset;

                    if (physicalSize >= 0)
                    {
                        physicalMetadata.Size = physicalSize;
                    }
                }
            }
            catch { }

            return(physicalMetadata);
        }
        public async Task <HttpResult> ProcessRequestAsync(IPAddress clientIpAddress, string requestPathAndQueryString, CancellationToken cancel = default)
        {
            clientIpAddress = clientIpAddress._UnmapIPv4();

            try
            {
                // クライアント IP による ACL のチェック
                if (Options.ClientIpAcl(clientIpAddress) == false)
                {
                    // ACL error
                    return(new HttpStringResult("403 Forbidden", statusCode: 403));
                }

                // URL のチェック
                requestPathAndQueryString._ParseUrl(out Uri uri, out QueryStringList qsList);

                string relativePath;

                if (this.AbsolutePathPrefix._IsFilled())
                {
                    // AbsolutePathPrefix の検査
                    if (uri.AbsolutePath._TryTrimStartWith(out relativePath, StringComparison.OrdinalIgnoreCase, this.AbsolutePathPrefix) == false)
                    {
                        // Not found
                        return(new HttpStringResult("404 Not Found", statusCode: 404));
                    }
                }
                else
                {
                    relativePath = uri.AbsolutePath;
                }

                if (relativePath.StartsWith("/") == false)
                {
                    relativePath = "/" + relativePath;
                }

                relativePath = PathParser.Linux.NormalizeUnixStylePathWithRemovingRelativeDirectoryElements(relativePath);

                if (RootFs.IsDirectoryExists(relativePath, cancel))
                {
                    // Directory
                    string htmlBody = BuildDirectoryHtml(new DirectoryPath(relativePath, RootFs));

                    return(new HttpStringResult(htmlBody, contentType: Consts.MimeTypes.HtmlUtf8));
                }
                else if (RootFs.IsFileExists(relativePath, cancel))
                {
                    // File
                    string extension = RootFs.PathParser.GetExtension(relativePath);
                    string mimeType  = MasterData.ExtensionToMime.Get(extension);

                    FileObject file = await RootFs.OpenAsync(relativePath, cancel : cancel);

                    try
                    {
                        long fileSize = file.Size;

                        long head = qsList._GetStrFirst("head")._ToInt()._NonNegative();
                        long tail = qsList._GetStrFirst("tail")._ToInt()._NonNegative();

                        if (head != 0 && tail != 0)
                        {
                            throw new ApplicationException("You can specify either head or tail.");
                        }

                        head = head._Min(fileSize);
                        tail = tail._Min(fileSize);

                        long readStart = 0;
                        long readSize  = fileSize;

                        if (head != 0)
                        {
                            readStart = 0;
                            readSize  = head;
                        }
                        else if (tail != 0)
                        {
                            readStart = fileSize - tail;
                            readSize  = tail;
                        }

                        if (tail != 0)
                        {
                            mimeType = Consts.MimeTypes.Text;
                        }

                        byte[] preData = new byte[0];

                        if (readSize != 0 && fileSize >= 3)
                        {
                            try
                            {
                                // 元のファイルの先頭に BOM が付いていて、先頭をスキップする場合は、
                                // 応答データに先頭にも BOM を付ける
                                byte[] bom = new byte[3];
                                if (await file.ReadRandomAsync(0, bom, cancel) == 3)
                                {
                                    if (Str.BOM_UTF_8._MemEquals(bom))
                                    {
                                        preData = bom;
                                    }
                                }
                            }
                            catch { }
                        }

                        return(new HttpFileResult(file, readStart, readSize, mimeType, preData: preData));
                    }
                    catch
                    {
                        file._DisposeSafe();
                        throw;
                    }
                }
                else
                {
                    // Not found
                    return(new HttpStringResult("404 Not Found", statusCode: 404));
                }
            }
            catch (Exception ex)
            {
                return(new HttpStringResult($"HTTP Status Code: 500\r\n" + ex.ToString(), statusCode: 500));
            }
        }