@@ -249,9 +249,24 @@ } return size, nil } - serveContent(w, req, name, modtime, sizeFunc, content) + serveContent(w, req, name, modtime, sizeFunc, content, StatusOK) } +func ServeContentCustomStatusCode(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker, code int) { + sizeFunc := func() (int64, error) { + size, err := content.Seek(0, io.SeekEnd) + if err != nil { + return 0, errSeeker + } + _, err = content.Seek(0, io.SeekStart) + if err != nil { + return 0, errSeeker + } + return size, nil + } + serveContent(w, req, name, modtime, sizeFunc, content, code) +} + // errSeeker is returned by ServeContent's sizeFunc when the content // doesn't seek properly. The underlying Seeker's error text isn't // included in the sizeFunc reply so it's not sent over HTTP to end @@ -266,15 +281,13 @@ // if modtime.IsZero(), modtime is unknown. // content must be seeked to the beginning of the file. // The sizeFunc is called at most once. Its error, if any, is sent in the HTTP response. -func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker) { +func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker, code int) { setLastModified(w, modtime) done, rangeReq := checkPreconditions(w, r, modtime) if done { return } - code := StatusOK - // If Content-Type isn't set, use the file's extension to find it, but // if the Content-Type is unset explicitly, do not sniff the type. ctypes, haveType := w.Header()["Content-Type"] @@ -671,7 +684,7 @@ } // name is '/'-separated, not filepath.Separator. -func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) { +func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool, statusCode int) { const indexPage = "/index.html" // redirect .../index.html to .../ @@ -753,7 +766,7 @@ // serveContent will check modification time sizeFunc := func() (int64, error) { return d.Size(), nil } - serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f) + serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f, statusCode) } // toHTTPError returns a non-specific HTTP error message and status code @@ -814,7 +827,21 @@ return } dir, file := filepath.Split(name) - serveFile(w, r, Dir(dir), file, false) + serveFile(w, r, Dir(dir), file, false, StatusOK) +} + +func ServeFileWithCustomStatusCode(w ResponseWriter, r *Request, name string, statusCode int) { + if containsDotDot(r.URL.Path) { + // Too many programs use r.URL.Path to construct the argument to + // serveFile. Reject the request under the assumption that happened + // here and ".." may not be wanted. + // Note that name might not contain "..", for example if code (still + // incorrectly) used filepath.Join(myDir, r.URL.Path). + serveError(w, "invalid URL path", StatusBadRequest) + return + } + dir, file := filepath.Split(name) + serveFile(w, r, Dir(dir), file, false, statusCode) } // ServeFileFS replies to the request with the contents @@ -847,7 +874,7 @@ serveError(w, "invalid URL path", StatusBadRequest) return } - serveFile(w, r, FS(fsys), name, false) + serveFile(w, r, FS(fsys), name, false, StatusOK) } func containsDotDot(v string) bool { @@ -983,7 +1010,7 @@ upath = "/" + upath r.URL.Path = upath } - serveFile(w, r, f.root, path.Clean(upath), true) + serveFile(w, r, f.root, path.Clean(upath), true, StatusOK) } // httpRange specifies the byte range to be sent to the client.