Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Windows support and removed some panics #1264

Merged
merged 1 commit into from Apr 9, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
117 changes: 68 additions & 49 deletions fs.go
Expand Up @@ -99,10 +99,13 @@ func ServeFile(ctx *RequestCtx, path string) {
rootFSOnce.Do(func() {
rootFSHandler = rootFS.NewRequestHandler()
})
if len(path) == 0 || path[0] != '/' {

if len(path) == 0 || !filepath.IsAbs(path) {
// extend relative path to absolute path
hasTrailingSlash := len(path) > 0 && path[len(path)-1] == '/'
hasTrailingSlash := len(path) > 0 && (path[len(path)-1] == '/' || path[len(path)-1] == '\\')

var err error
path = filepath.FromSlash(path)
if path, err = filepath.Abs(path); err != nil {
ctx.Logger().Printf("cannot resolve path %q to absolute file path: %v", path, err)
ctx.Error("Internal Server Error", StatusInternalServerError)
Expand All @@ -112,14 +115,19 @@ func ServeFile(ctx *RequestCtx, path string) {
path += "/"
}
}

// convert the path to forward slashes regardless the OS in order to set the URI properly
// the handler will convert back to OS path separator before opening the file
path = filepath.ToSlash(path)

ctx.Request.SetRequestURI(path)
rootFSHandler(ctx)
}

var (
rootFSOnce sync.Once
rootFS = &FS{
Root: "/",
Root: "",
GenerateIndexPages: true,
Compress: true,
CompressBrotli: true,
Expand Down Expand Up @@ -376,13 +384,22 @@ func (fs *FS) NewRequestHandler() RequestHandler {
func (fs *FS) initRequestHandler() {
root := fs.Root

// serve files from the current working directory if root is empty
if len(root) == 0 {
root = "."
// rootFS' handleRequest will do special treatment of absolute and relative paths
if fs != rootFS {
// serve files from the current working directory if root is empty
if len(root) == 0 || !filepath.IsAbs(root) {
path, err := os.Getwd()
if err != nil {
path = "."
}
root = path + "/" + root
}
// convert the root directory slashes to the native format
root = filepath.FromSlash(root)
}

// strip trailing slashes from the root path
for len(root) > 0 && root[len(root)-1] == '/' {
for len(root) > 0 && root[len(root)-1] == os.PathSeparator {
root = root[:len(root)-1]
}

Expand Down Expand Up @@ -495,10 +512,10 @@ func (ff *fsFile) NewReader() (io.Reader, error) {
}
return r, err
}
return ff.smallFileReader(), nil
return ff.smallFileReader()
}

func (ff *fsFile) smallFileReader() io.Reader {
func (ff *fsFile) smallFileReader() (io.Reader, error) {
v := ff.h.smallFileReaderPool.Get()
if v == nil {
v = &fsSmallFileReader{}
Expand All @@ -507,9 +524,9 @@ func (ff *fsFile) smallFileReader() io.Reader {
r.ff = ff
r.endPos = ff.contentLength
if r.startPos > 0 {
panic("BUG: fsSmallFileReader with non-nil startPos found in the pool")
return nil, errors.New("bug: fsSmallFileReader with non-nil startPos found in the pool")
}
return r
return r, nil
}

// files bigger than this size are sent with sendfile
Expand All @@ -521,7 +538,7 @@ func (ff *fsFile) isBig() bool {

func (ff *fsFile) bigFileReader() (io.Reader, error) {
if ff.f == nil {
panic("BUG: ff.f must be non-nil in bigFileReader")
return nil, errors.New("bug: ff.f must be non-nil in bigFileReader")
}

var r io.Reader
Expand Down Expand Up @@ -551,12 +568,12 @@ func (ff *fsFile) bigFileReader() (io.Reader, error) {

func (ff *fsFile) Release() {
if ff.f != nil {
ff.f.Close()
_ = ff.f.Close()

if ff.isBig() {
ff.bigFilesLock.Lock()
for _, r := range ff.bigFiles {
r.f.Close()
_ = r.f.Close()
}
ff.bigFilesLock.Unlock()
}
Expand Down Expand Up @@ -597,7 +614,7 @@ func (r *bigFileReader) Read(p []byte) (int, error) {

func (r *bigFileReader) WriteTo(w io.Writer) (int64, error) {
if rf, ok := w.(io.ReaderFrom); ok {
// fast path. Senfile must be triggered
// fast path. Send file must be triggered
return rf.ReadFrom(r.r)
}

Expand All @@ -609,16 +626,17 @@ func (r *bigFileReader) Close() error {
r.r = r.f
n, err := r.f.Seek(0, 0)
if err == nil {
if n != 0 {
panic("BUG: File.Seek(0,0) returned (non-zero, nil)")
if n == 0 {
ff := r.ff
ff.bigFilesLock.Lock()
ff.bigFiles = append(ff.bigFiles, r)
ff.bigFilesLock.Unlock()
} else {
_ = r.f.Close()
err = errors.New("bug: File.Seek(0,0) returned (non-zero, nil)")
}

ff := r.ff
ff.bigFilesLock.Lock()
ff.bigFiles = append(ff.bigFiles, r)
ff.bigFilesLock.Unlock()
} else {
r.f.Close()
_ = r.f.Close()
}
r.ff.decReadersCount()
return err
Expand Down Expand Up @@ -696,7 +714,7 @@ func (r *fsSmallFileReader) WriteTo(w io.Writer) (int64, error) {
nw, errw := w.Write(buf[:n])
curPos += nw
if errw == nil && nw != n {
panic("BUG: Write(p) returned (n, nil), where n != len(p)")
errw = errors.New("bug: Write(p) returned (n, nil), where n != len(p)")
}
if err == nil {
err = errw
Expand Down Expand Up @@ -809,7 +827,8 @@ func (h *fsHandler) handleRequest(ctx *RequestCtx) {

if !ok {
pathStr := string(path)
filePath := h.root + pathStr
filePath := filepath.FromSlash(h.root + pathStr)

var err error
ff, err = h.openFSFile(filePath, mustCompress, fileEncoding)
if mustCompress && err == errNoCreatePermission {
Expand Down Expand Up @@ -888,14 +907,14 @@ func (h *fsHandler) handleRequest(ctx *RequestCtx) {
if len(byteRange) > 0 {
startPos, endPos, err := ParseByteRange(byteRange, contentLength)
if err != nil {
r.(io.Closer).Close()
_ = r.(io.Closer).Close()
ctx.Logger().Printf("cannot parse byte range %q for path=%q: %v", byteRange, path, err)
ctx.Error("Range Not Satisfiable", StatusRequestedRangeNotSatisfiable)
return
}

if err = r.(byteRangeUpdater).UpdateByteRange(startPos, endPos); err != nil {
r.(io.Closer).Close()
_ = r.(io.Closer).Close()
ctx.Logger().Printf("cannot seek byte range %q for path=%q: %v", byteRange, path, err)
ctx.Error("Internal Server Error", StatusInternalServerError)
return
Expand Down Expand Up @@ -1017,16 +1036,16 @@ func (h *fsHandler) createDirIndex(base *URI, dirPath string, mustCompress bool,
w := &bytebufferpool.ByteBuffer{}

basePathEscaped := html.EscapeString(string(base.Path()))
fmt.Fprintf(w, "<html><head><title>%s</title><style>.dir { font-weight: bold }</style></head><body>", basePathEscaped)
fmt.Fprintf(w, "<h1>%s</h1>", basePathEscaped)
fmt.Fprintf(w, "<ul>")
_, _ = fmt.Fprintf(w, "<html><head><title>%s</title><style>.dir { font-weight: bold }</style></head><body>", basePathEscaped)
_, _ = fmt.Fprintf(w, "<h1>%s</h1>", basePathEscaped)
_, _ = fmt.Fprintf(w, "<ul>")

if len(basePathEscaped) > 1 {
var parentURI URI
base.CopyTo(&parentURI)
parentURI.Update(string(base.Path()) + "/..")
parentPathEscaped := html.EscapeString(string(parentURI.Path()))
fmt.Fprintf(w, `<li><a href="%s" class="dir">..</a></li>`, parentPathEscaped)
_, _ = fmt.Fprintf(w, `<li><a href="%s" class="dir">..</a></li>`, parentPathEscaped)
}

f, err := os.Open(dirPath)
Expand All @@ -1035,7 +1054,7 @@ func (h *fsHandler) createDirIndex(base *URI, dirPath string, mustCompress bool,
}

fileinfos, err := f.Readdir(0)
f.Close()
_ = f.Close()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1070,11 +1089,11 @@ nestedContinue:
auxStr = fmt.Sprintf("file, %d bytes", fi.Size())
className = "file"
}
fmt.Fprintf(w, `<li><a href="%s" class="%s">%s</a>, %s, last modified %s</li>`,
_, _ = fmt.Fprintf(w, `<li><a href="%s" class="%s">%s</a>, %s, last modified %s</li>`,
pathEscaped, className, html.EscapeString(name), auxStr, fsModTime(fi.ModTime()))
}

fmt.Fprintf(w, "</ul></body></html>")
_, _ = fmt.Fprintf(w, "</ul></body></html>")

if mustCompress {
var zbuf bytebufferpool.ByteBuffer
Expand Down Expand Up @@ -1115,12 +1134,12 @@ func (h *fsHandler) compressAndOpenFSFile(filePath string, fileEncoding string)

fileInfo, err := f.Stat()
if err != nil {
f.Close()
_ = f.Close()
return nil, fmt.Errorf("cannot obtain info for file %q: %w", filePath, err)
}

if fileInfo.IsDir() {
f.Close()
_ = f.Close()
return nil, errDirIndexRequired
}

Expand All @@ -1133,7 +1152,7 @@ func (h *fsHandler) compressAndOpenFSFile(filePath string, fileEncoding string)
compressedFilePath := filePath + h.compressedFileSuffixes[fileEncoding]
absPath, err := filepath.Abs(compressedFilePath)
if err != nil {
f.Close()
_ = f.Close()
return nil, fmt.Errorf("cannot determine absolute path for %q: %v", compressedFilePath, err)
}

Expand All @@ -1151,7 +1170,7 @@ func (h *fsHandler) compressFileNolock(f *os.File, fileInfo os.FileInfo, filePat
// It is safe opening such a file, since the file creation
// is guarded by file mutex - see getFileLock call.
if _, err := os.Stat(compressedFilePath); err == nil {
f.Close()
_ = f.Close()
return h.newCompressedFSFile(compressedFilePath, fileEncoding)
}

Expand All @@ -1160,7 +1179,7 @@ func (h *fsHandler) compressFileNolock(f *os.File, fileInfo os.FileInfo, filePat
tmpFilePath := compressedFilePath + ".tmp"
zf, err := os.Create(tmpFilePath)
if err != nil {
f.Close()
_ = f.Close()
if !os.IsPermission(err) {
return nil, fmt.Errorf("cannot create temporary file %q: %w", tmpFilePath, err)
}
Expand All @@ -1181,8 +1200,8 @@ func (h *fsHandler) compressFileNolock(f *os.File, fileInfo os.FileInfo, filePat
}
releaseStacklessGzipWriter(zw, CompressDefaultCompression)
}
zf.Close()
f.Close()
_ = zf.Close()
_ = f.Close()
if err != nil {
return nil, fmt.Errorf("error when compressing file %q to %q: %w", filePath, tmpFilePath, err)
}
Expand All @@ -1203,7 +1222,7 @@ func (h *fsHandler) newCompressedFSFile(filePath string, fileEncoding string) (*
}
fileInfo, err := f.Stat()
if err != nil {
f.Close()
_ = f.Close()
return nil, fmt.Errorf("cannot obtain info for compressed file %q: %w", filePath, err)
}
return h.newFSFile(f, fileInfo, true, fileEncoding)
Expand All @@ -1225,12 +1244,12 @@ func (h *fsHandler) openFSFile(filePath string, mustCompress bool, fileEncoding

fileInfo, err := f.Stat()
if err != nil {
f.Close()
_ = f.Close()
return nil, fmt.Errorf("cannot obtain info for file %q: %w", filePath, err)
}

if fileInfo.IsDir() {
f.Close()
_ = f.Close()
if mustCompress {
return nil, fmt.Errorf("directory with unexpected suffix found: %q. Suffix: %q",
filePath, h.compressedFileSuffixes[fileEncoding])
Expand All @@ -1241,7 +1260,7 @@ func (h *fsHandler) openFSFile(filePath string, mustCompress bool, fileEncoding
if mustCompress {
fileInfoOriginal, err := os.Stat(filePathOriginal)
if err != nil {
f.Close()
_ = f.Close()
return nil, fmt.Errorf("cannot obtain info for original file %q: %w", filePathOriginal, err)
}

Expand All @@ -1250,8 +1269,8 @@ func (h *fsHandler) openFSFile(filePath string, mustCompress bool, fileEncoding
// to look newer than the gzipped file.
if fileInfoOriginal.ModTime().Sub(fileInfo.ModTime()) >= time.Second {
// The compressed file became stale. Re-create it.
f.Close()
os.Remove(filePath)
_ = f.Close()
_ = os.Remove(filePath)
return h.compressAndOpenFSFile(filePathOriginal, fileEncoding)
}
}
Expand All @@ -1263,7 +1282,7 @@ func (h *fsHandler) newFSFile(f *os.File, fileInfo os.FileInfo, compressed bool,
n := fileInfo.Size()
contentLength := int(n)
if n != int64(contentLength) {
f.Close()
_ = f.Close()
return nil, fmt.Errorf("too big file: %d bytes", n)
}

Expand Down Expand Up @@ -1375,7 +1394,7 @@ func FileLastModified(path string) (time.Time, error) {
return zeroTime, err
}
fileInfo, err := f.Stat()
f.Close()
_ = f.Close()
if err != nil {
return zeroTime, err
}
Expand Down