diff options
| author | Shulhan <ms@kilabit.info> | 2026-02-05 19:26:29 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2026-02-05 19:26:29 +0700 |
| commit | 3734420eec485bca8c18243741e1ad2a683515b7 (patch) | |
| tree | c08f208e76f516048e07e982fa712daafc98cce3 /lib/http/server.go | |
| parent | f40153cfb1cd859538aeaad17f0a243b4ef0c52c (diff) | |
| download | pakakeh.go-3734420eec485bca8c18243741e1ad2a683515b7.tar.xz | |
lib/http: handle file system with canonical directory end with slash "/"
Previously, if request to directory does not end with "/", the HTTP
server will return the index.html of that directory.
This cause relative link inside the index.html broken when visited from
browser.
This changes make the request to directory always end with "/" by
redirecting the request with status 303 Found.
Diffstat (limited to 'lib/http/server.go')
| -rw-r--r-- | lib/http/server.go | 107 |
1 files changed, 47 insertions, 60 deletions
diff --git a/lib/http/server.go b/lib/http/server.go index 76713308..edc99cf7 100644 --- a/lib/http/server.go +++ b/lib/http/server.go @@ -12,6 +12,7 @@ import ( "io" "log" "net/http" + "net/url" "os" "path" "sort" @@ -384,60 +385,67 @@ func (srv *Server) Stop(wait time.Duration) (err error) { // getFSNode get the memfs Node based on the request path. // -// If the path is not exist, try path with index.html; -// if it still not exist try path with suffix .html. +// If the path is not exist, try path with ".html". // -// If the path is directory and contains index.html, the node for index.html -// with true will be returned. +// If the path is directory and contains index.html, the node content for +// index.html will be returned. // // If the path is directory and does not contains index.html and // [ServerOptions.EnableIndexHTML] is true, server will generate list of // content for index.html. -func (srv *Server) getFSNode(reqPath string) (node *memfs.Node) { +// +// The redirectPath return whether the server should redirect first before +// serving the content to provide consistent canonical path that end with "/" +// for directory that contains "index.html" or EnableIndexHTML is on. +func (srv *Server) getFSNode(reqURL url.URL) (node *memfs.Node, redirectURL *url.URL) { if srv.Options.Memfs == nil { - return nil + return nil, nil } + reqPath := reqURL.Path + endWithSlash := reqPath[len(reqPath)-1] == '/' var err error + // The [MemFS.Get] method check reqPath with trailing `/` removed, so + // "path" or "path/" will return the same node. node, err = srv.Options.Memfs.Get(reqPath) if err != nil { if !errors.Is(err, os.ErrNotExist) { - return nil + return nil, nil } - - var pathHTML = path.Join(reqPath, `index.html`) - - node, err = srv.Options.Memfs.Get(pathHTML) - if err != nil { - pathHTML = reqPath + `.html` + if !endWithSlash { + pathHTML := reqPath + `.html` node, err = srv.Options.Memfs.Get(pathHTML) if err != nil { - return nil + return nil, nil } - return node + return node, nil } + return nil, nil + } + if !node.IsDir() { + return node, nil } - if node.IsDir() { - var ( - pathHTML = path.Join(reqPath, `index.html`) - nodeIndexHTML *memfs.Node - ) - - nodeIndexHTML, err = srv.Options.Memfs.Get(pathHTML) - if err == nil { - return nodeIndexHTML - } - - if !srv.Options.EnableIndexHTML { - return node + pathIndexHTML := path.Join(reqPath, `index.html`) + nodeIndexHTML, err := srv.Options.Memfs.Get(pathIndexHTML) + if err != nil { + if srv.Options.EnableIndexHTML { + if !endWithSlash { + redirectURL = reqURL.JoinPath(`/`) + return nil, redirectURL + } + node.GenerateIndexHTML() + return node, nil } - - node.GenerateIndexHTML() + return nil, nil + } + if !endWithSlash { + redirectURL = reqURL.JoinPath(`/`) + return nil, redirectURL } - return node + return nodeIndexHTML, nil } // handleDelete handle the DELETE request by searching the registered route @@ -468,32 +476,14 @@ func (srv *Server) handleDelete(res http.ResponseWriter, req *http.Request) { // // If the request Path is not exist it will return 404 Not Found. func (srv *Server) HandleFS(res http.ResponseWriter, req *http.Request) { - var ( - logp = "HandleFS" - - node *memfs.Node - err error - ) - - node = srv.getFSNode(req.URL.Path) - if node == nil { - if srv.Options.HandleFS == nil { - res.WriteHeader(http.StatusNotFound) - return - } - // Fallthrough, call HandleFS below. - } else if node.IsDir() && req.URL.Path[len(req.URL.Path)-1] != '/' { + node, redirectURL := srv.getFSNode(*req.URL) + if redirectURL != nil { // If request path is a directory and it is not end with // slash, redirect request to location with slash to allow // relative links works inside the HTML content. - var redirectPath = req.URL.Path + "/" - if len(req.URL.RawQuery) > 0 { - redirectPath += "?" + req.URL.RawQuery - } - http.Redirect(res, req, redirectPath, http.StatusFound) + http.Redirect(res, req, redirectURL.String(), http.StatusFound) return } - if srv.Options.HandleFS != nil { var statusCode int node, statusCode = srv.Options.HandleFS(node, res, req) @@ -522,9 +512,7 @@ func (srv *Server) HandleFS(res http.ResponseWriter, req *http.Request) { var ifModifiedSince = req.Header.Get(HeaderIfModifiedSince) if len(ifModifiedSince) != 0 { - var timeModsince time.Time - - timeModsince, err = time.Parse(time.RFC1123, ifModifiedSince) + timeModsince, err := time.Parse(time.RFC1123, ifModifiedSince) if err == nil { if nodeModtime <= timeModsince.Unix() { res.WriteHeader(http.StatusNotModified) @@ -542,8 +530,7 @@ func (srv *Server) HandleFS(res http.ResponseWriter, req *http.Request) { bodyReader = bytes.NewReader(node.Content) size = node.Size() } else { - var f *os.File - f, err = os.Open(node.SysPath) + f, err := os.Open(node.SysPath) if err != nil { res.WriteHeader(http.StatusInternalServerError) return @@ -577,7 +564,7 @@ func (srv *Server) HandleFS(res http.ResponseWriter, req *http.Request) { return } - responseWrite(logp, res, req, bodyReader) + responseWrite(`HandleFS`, res, req, bodyReader) } // handleGet handle the GET request by searching the registered route and @@ -653,8 +640,8 @@ func (srv *Server) handleHead(res http.ResponseWriter, req *http.Request) { func (srv *Server) handleOptions(res http.ResponseWriter, req *http.Request) { methods := make(map[string]bool) - var node = srv.getFSNode(req.URL.Path) - if node != nil { + node, redirectURL := srv.getFSNode(*req.URL) + if node != nil || redirectURL != nil { methods[http.MethodGet] = true methods[http.MethodHead] = true } |
