diff options
Diffstat (limited to 'lib/http')
| -rw-r--r-- | lib/http/server.go | 107 | ||||
| -rw-r--r-- | lib/http/server_test.go | 90 | ||||
| -rw-r--r-- | lib/http/testdata/Server_HandleFS/b.html | 6 | ||||
| -rw-r--r-- | lib/http/testdata/Server_HandleFS/dir/index.html | 6 | ||||
| -rw-r--r-- | lib/http/testdata/Server_HandleFS/index.html | 6 | ||||
| -rw-r--r-- | lib/http/testdata/Server_HandleFS_test.txt | 98 |
6 files changed, 216 insertions, 97 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 } diff --git a/lib/http/server_test.go b/lib/http/server_test.go index 6542d833..a333628c 100644 --- a/lib/http/server_test.go +++ b/lib/http/server_test.go @@ -1,6 +1,5 @@ -// SPDX-FileCopyrightText: 2018 M. Shulhan <ms@kilabit.info> -// // SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2018 M. Shulhan <ms@kilabit.info> package http @@ -869,7 +868,8 @@ func TestServer_HandleFS(t *testing.T) { t.Fatal(err) } - var root = t.TempDir() + root := `testdata/Server_HandleFS` + var mfsOpts = memfs.Options{ Root: root, MaxFileSize: -1, @@ -892,48 +892,90 @@ func TestServer_HandleFS(t *testing.T) { t.Fatal(err) } - var redactTime = regexp.MustCompile(`\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d`) - var redactedTime = []byte(`0000-00-00T00:00:00`) - var simreq = libhttptest.SimulateRequest{ - Path: `/`, - } - var simres *libhttptest.SimulateResult - var exp string - var got []byte + listCase := []struct { + path string + expResponse string + }{{ + path: `/`, + expResponse: string(tdata.Output[`/`]), + }, { + path: `/index`, + expResponse: string(tdata.Output[`/`]), + }, { + path: `/dir`, + expResponse: string(tdata.Output[`/dir`]), + }, { + path: `/dir?q=abc`, + expResponse: string(tdata.Output[`/dir?q=abc`]), + }, { + path: `/dir?q=abc#fgh`, + expResponse: string(tdata.Output[`/dir?q=abc#fgh`]), + }, { + path: `/dir#fgh`, + expResponse: string(tdata.Output[`/dir/#fgh`]), + }, { + path: `/dir/#fgh`, + expResponse: string(tdata.Output[`/dir/#fgh`]), + }, { + path: `/dir/`, + expResponse: string(tdata.Output[`/dir/`]), + }, { + path: `/b`, + expResponse: string(tdata.Output[`/b`]), + }, { + path: `/b?q=abc`, + expResponse: string(tdata.Output[`/b`]), + }, { + path: `/b?q=abc#fgh`, + expResponse: string(tdata.Output[`/b`]), + }} + + for _, tc := range listCase { + simreq := libhttptest.SimulateRequest{ + Path: tc.path, + } - t.Run(`OnEmptyRoot`, func(t *testing.T) { + var simres *libhttptest.SimulateResult simres, err = libhttptest.Simulate(httpd.ServeHTTP, &simreq) if err != nil { t.Fatal(err) } - exp = string(tdata.Output[t.Name()]) + var got []byte got, err = simres.DumpResponse([]string{HeaderETag}) if err != nil { t.Fatal(err) } - test.Assert(t, `response`, exp, string(got)) - }) + test.Assert(t, tc.path, tc.expResponse, string(got)) + } + + newDir := filepath.Join(root, `newDir`) + _ = os.RemoveAll(newDir) + + t.Run(`OnNewDirectory`, func(tt *testing.T) { + redactTime := regexp.MustCompile(`\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d`) + redactedTime := []byte(`0000-00-00T00:00:00`) - t.Run(`OnNewDirectory`, func(t *testing.T) { - var newDir = filepath.Join(root, `dirA`) err = os.MkdirAll(newDir, 0755) if err != nil { - t.Fatal(err) + tt.Fatal(err) } - simres, err = libhttptest.Simulate(httpd.ServeHTTP, &simreq) + simreq := libhttptest.SimulateRequest{ + Path: `/newDir/`, + } + simres, err := libhttptest.Simulate(httpd.ServeHTTP, &simreq) if err != nil { - t.Fatal(err) + tt.Fatal(err) } - exp = string(tdata.Output[t.Name()]) - got, err = simres.DumpResponse([]string{HeaderETag}) + exp := string(tdata.Output[tt.Name()]) + got, err := simres.DumpResponse([]string{HeaderETag}) if err != nil { - t.Fatal(err) + tt.Fatal(err) } got = redactTime.ReplaceAll(got, redactedTime) - test.Assert(t, `response`, exp, string(got)) + test.Assert(tt, tt.Name(), exp, string(got)) }) } diff --git a/lib/http/testdata/Server_HandleFS/b.html b/lib/http/testdata/Server_HandleFS/b.html new file mode 100644 index 00000000..aa8d367a --- /dev/null +++ b/lib/http/testdata/Server_HandleFS/b.html @@ -0,0 +1,6 @@ +<!doctype html> +<html> + <head> + <title>/b.html</title> + </head> +</html> diff --git a/lib/http/testdata/Server_HandleFS/dir/index.html b/lib/http/testdata/Server_HandleFS/dir/index.html new file mode 100644 index 00000000..245518a0 --- /dev/null +++ b/lib/http/testdata/Server_HandleFS/dir/index.html @@ -0,0 +1,6 @@ +<!doctype html> +<html> + <head> + <title>/dir/index.html</title> + </head> +</html> diff --git a/lib/http/testdata/Server_HandleFS/index.html b/lib/http/testdata/Server_HandleFS/index.html new file mode 100644 index 00000000..73b9ab05 --- /dev/null +++ b/lib/http/testdata/Server_HandleFS/index.html @@ -0,0 +1,6 @@ +<!doctype html> +<html> + <head> + <title>/index.html</title> + </head> +</html> diff --git a/lib/http/testdata/Server_HandleFS_test.txt b/lib/http/testdata/Server_HandleFS_test.txt index 1089121a..8b2173ef 100644 --- a/lib/http/testdata/Server_HandleFS_test.txt +++ b/lib/http/testdata/Server_HandleFS_test.txt @@ -1,21 +1,93 @@ // SPDX-License-Identifier: BSD-3-Clause // SPDX-FileCopyrightText: 2025 M. Shulhan <ms@kilabit.info> -<<< TestServer_HandleFS/OnEmptyRoot +<<< / HTTP/1.1 200 OK Connection: close Content-Type: text/html; charset=utf-8 -<!DOCTYPE html><html> -<head> -<meta name="viewport" content="width=device-width"> -<style> -body{font-family:monospace; white-space:pre;} -</style> -</head> -<body> -<h3>Index of /</h3> -</body></html> +<!doctype html> +<html> + <head> + <title>/index.html</title> + </head> +</html> + +<<< /dir +HTTP/1.1 302 Found +Connection: close +Content-Type: text/html; charset=utf-8 +Location: /dir/ + +<a href="/dir/">Found</a>. + + + +<<< /dir?q=abc +HTTP/1.1 302 Found +Connection: close +Content-Type: text/html; charset=utf-8 +Location: /dir/?q=abc + +<a href="/dir/?q=abc">Found</a>. + + + +<<< /dir?q=abc#fgh +HTTP/1.1 302 Found +Connection: close +Content-Type: text/html; charset=utf-8 +Location: /dir/?q=abc#fgh + +<a href="/dir/?q=abc#fgh">Found</a>. + + + +<<< /dir/#fgh +HTTP/1.1 404 Not Found +Connection: close + + + +<<< /dir/ +HTTP/1.1 200 OK +Connection: close +Content-Type: text/html; charset=utf-8 + +<!doctype html> +<html> + <head> + <title>/dir/index.html</title> + </head> +</html> + + +<<< /b +HTTP/1.1 200 OK +Connection: close +Content-Type: text/html; charset=utf-8 + +<!doctype html> +<html> + <head> + <title>/b.html</title> + </head> +</html> + + +<<< /b?q=abc +HTTP/1.1 200 OK +Connection: close +Content-Type: text/html; charset=utf-8 + +<!doctype html> +<html> + <head> + <title>/b.html</title> + </head> +</html> + + <<< TestServer_HandleFS/OnNewDirectory HTTP/1.1 200 OK @@ -30,6 +102,6 @@ body{font-family:monospace; white-space:pre;} </style> </head> <body> -<h3>Index of /</h3> -<div>drwxr-xr-x <tt> 0</tt> 0000-00-00T00:00:00Z <a href="/dirA">dirA</a></div><br/> +<h3>Index of newDir</h3> +<div><a href='..'>..</a></div><br/> </body></html> |
