improve: 404 status code

This commit is contained in:
naiba 2024-12-31 23:58:44 +08:00
parent 553f8e58d4
commit 828588eef1
5 changed files with 135 additions and 19 deletions

View File

@ -48,7 +48,12 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: "1.23.x" go-version: "1.23.1"
- name: patch net/http/fs.go
run: |
patch -p0 --forward `go env GOROOT`/src/net/http/fs.go < ./script/patch/fs.patch || true
patch -p0 --forward `go env GOMODCACHE`/github.com/gin-gonic/gin@v1.10.0/context.go < ./script/patch/gin-context.patch || true
- name: generate swagger docs - name: generate swagger docs
run: | run: |

View File

@ -27,7 +27,12 @@ jobs:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: "1.23.x" go-version: "1.23.1"
- name: patch net/http/fs.go
run: |
patch -p0 --forward `go env GOROOT`/src/net/http/fs.go < ./script/patch/fs.patch || true
patch -p0 --forward `go env GOMODCACHE`/github.com/gin-gonic/gin@v1.10.0/context.go < ./script/patch/gin-context.patch || true
- name: generate swagger docs - name: generate swagger docs
run: | run: |

View File

@ -21,6 +21,7 @@ import (
"github.com/nezhahq/nezha/cmd/dashboard/controller/waf" "github.com/nezhahq/nezha/cmd/dashboard/controller/waf"
docs "github.com/nezhahq/nezha/cmd/dashboard/docs" docs "github.com/nezhahq/nezha/cmd/dashboard/docs"
"github.com/nezhahq/nezha/model" "github.com/nezhahq/nezha/model"
"github.com/nezhahq/nezha/pkg/utils"
"github.com/nezhahq/nezha/service/singleton" "github.com/nezhahq/nezha/service/singleton"
) )
@ -281,9 +282,9 @@ func getUid(c *gin.Context) uint64 {
} }
func fallbackToFrontend(frontendDist fs.FS) func(*gin.Context) { func fallbackToFrontend(frontendDist fs.FS) func(*gin.Context) {
checkLocalFileOrFs := func(c *gin.Context, fs fs.FS, path string) bool { checkLocalFileOrFs := func(c *gin.Context, fs fs.FS, path string, customStatusCode int) bool {
if _, err := os.Stat(path); err == nil { if _, err := os.Stat(path); err == nil {
c.File(path) c.FileWithCustomStatusCode(path, customStatusCode)
return true return true
} }
f, err := fs.Open(path) f, err := fs.Open(path)
@ -298,7 +299,7 @@ func fallbackToFrontend(frontendDist fs.FS) func(*gin.Context) {
if fileStat.IsDir() { if fileStat.IsDir() {
return false return false
} }
http.ServeContent(c.Writer, c.Request, path, fileStat.ModTime(), f.(io.ReadSeeker)) http.ServeContentCustomStatusCode(c.Writer, c.Request, path, fileStat.ModTime(), f.(io.ReadSeeker), customStatusCode)
return true return true
} }
return func(c *gin.Context) { return func(c *gin.Context) {
@ -309,29 +310,21 @@ func fallbackToFrontend(frontendDist fs.FS) func(*gin.Context) {
if strings.HasPrefix(c.Request.URL.Path, "/dashboard") { if strings.HasPrefix(c.Request.URL.Path, "/dashboard") {
stripPath := strings.TrimPrefix(c.Request.URL.Path, "/dashboard") stripPath := strings.TrimPrefix(c.Request.URL.Path, "/dashboard")
localFilePath := path.Join(singleton.Conf.AdminTemplate, stripPath) localFilePath := path.Join(singleton.Conf.AdminTemplate, stripPath)
if stripPath == "/" { statusCode := utils.IfOr(stripPath == "/", http.StatusOK, http.StatusNotFound)
c.Status(http.StatusOK) if checkLocalFileOrFs(c, frontendDist, localFilePath, http.StatusOK) {
}
if checkLocalFileOrFs(c, frontendDist, localFilePath) {
return return
} else {
c.Status(http.StatusNotFound)
} }
if !checkLocalFileOrFs(c, frontendDist, singleton.Conf.AdminTemplate+"/index.html") { if !checkLocalFileOrFs(c, frontendDist, singleton.Conf.AdminTemplate+"/index.html", statusCode) {
c.JSON(http.StatusNotFound, newErrorResponse(errors.New("404 Not Found"))) c.JSON(http.StatusNotFound, newErrorResponse(errors.New("404 Not Found")))
} }
return return
} }
localFilePath := path.Join(singleton.Conf.UserTemplate, c.Request.URL.Path) localFilePath := path.Join(singleton.Conf.UserTemplate, c.Request.URL.Path)
if c.Request.URL.Path == "/" { if checkLocalFileOrFs(c, frontendDist, localFilePath, http.StatusOK) {
c.Status(http.StatusOK)
}
if checkLocalFileOrFs(c, frontendDist, localFilePath) {
return return
} else {
c.Status(http.StatusNotFound)
} }
if !checkLocalFileOrFs(c, frontendDist, singleton.Conf.UserTemplate+"/index.html") { statusCode := utils.IfOr(c.Request.URL.Path == "/", http.StatusOK, http.StatusNotFound)
if !checkLocalFileOrFs(c, frontendDist, singleton.Conf.UserTemplate+"/index.html", statusCode) {
c.JSON(http.StatusNotFound, newErrorResponse(errors.New("404 Not Found"))) c.JSON(http.StatusNotFound, newErrorResponse(errors.New("404 Not Found")))
} }
} }

102
script/patch/fs.patch Normal file
View File

@ -0,0 +1,102 @@
@@ -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.

View File

@ -0,0 +1,11 @@
@@ -1073,6 +1073,10 @@
// File writes the specified file into the body stream in an efficient way.
func (c *Context) File(filepath string) {
http.ServeFile(c.Writer, c.Request, filepath)
+}
+
+func (c *Context) FileWithCustomStatusCode(filepath string, statusCode int) {
+ http.ServeFileWithCustomStatusCode(c.Writer, c.Request, filepath, statusCode)
}
// FileFromFS writes the specified file from http.FileSystem into the body stream in an efficient way.