From 0b744eba7e7d4c725941ecff2132c75929b72f02 Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Thu, 5 Nov 2020 10:00:32 -0800 Subject: [PATCH] Redirect requests with invalid trailing slash --- CONFIGURATION.md | 2 +- config.go | 2 - server_command.go => serve_command.go | 0 server_fcgi.go => serve_fcgi.go | 0 server_file.go => serve_file.go | 63 +++++------------------ server_proxy.go => serve_proxy.go | 0 server.go | 74 +++++++++++++++++++++++---- 7 files changed, 78 insertions(+), 63 deletions(-) rename server_command.go => serve_command.go (100%) rename server_fcgi.go => serve_fcgi.go (100%) rename server_file.go => serve_file.go (69%) rename server_proxy.go => serve_proxy.go (100%) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 875d690..8830737 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -65,7 +65,7 @@ and the private key file at `certs/live/$DOMAIN/privkey.pem` to twins. ### DisableSize The size of the response body is included in the media type header by default. -Set this option to `true` to disable this feature. +Set this option to `true` to disable this feature. ### Path diff --git a/config.go b/config.go index 9d59800..38c0665 100644 --- a/config.go +++ b/config.go @@ -126,8 +126,6 @@ func readconfig(configPath string) error { if serve.Path[0] == '^' { serve.r = regexp.MustCompile(serve.Path) - } else if serve.Path[len(serve.Path)-1] == '/' { - serve.Path = serve.Path[:len(serve.Path)-1] } if serve.FastCGI != "" { diff --git a/server_command.go b/serve_command.go similarity index 100% rename from server_command.go rename to serve_command.go diff --git a/server_fcgi.go b/serve_fcgi.go similarity index 100% rename from server_fcgi.go rename to serve_fcgi.go diff --git a/server_file.go b/serve_file.go similarity index 69% rename from server_file.go rename to serve_file.go index 8e034ad..8a260af 100644 --- a/server_file.go +++ b/serve_file.go @@ -6,7 +6,6 @@ import ( "net" "net/url" "os" - "path" "path/filepath" "sort" "strings" @@ -54,26 +53,18 @@ func serveDirList(c net.Conn, request *url.URL, dirPath string) { writeHeader(c, statusSuccess, "text/gemini; charset=utf-8") fmt.Fprintf(c, "# %s\r\n", request.Path) - if numDirs > 0 || numFiles > 0 { - if numDirs > 0 { - if numDirs == 1 { - c.Write([]byte("1 directory")) - } else { - fmt.Fprintf(c, "%d directories", numDirs) - } - } - if numFiles > 0 { - if numDirs > 0 { - c.Write([]byte(" and ")) - } - if numDirs == 1 { - c.Write([]byte("1 file")) - } else { - fmt.Fprintf(c, "%d files", numFiles) - } - } - c.Write([]byte("\r\n\n")) + if numDirs == 1 { + c.Write([]byte("1 directory")) + } else { + fmt.Fprintf(c, "%d directories", numDirs) } + c.Write([]byte(", ")) + if numDirs == 1 { + c.Write([]byte("1 file")) + } else { + fmt.Fprintf(c, "%d files", numFiles) + } + c.Write([]byte("\r\n\n")) if request.Path != "/" { c.Write([]byte("=> ../ ../\r\n\r\n")) @@ -102,37 +93,7 @@ func serveDirList(c net.Conn, request *url.URL, dirPath string) { } } -func serveFile(c net.Conn, request *url.URL, filePath string, listDir bool) { - fi, err := os.Stat(filePath) - if err != nil { - writeStatus(c, statusNotFound) - return - } - - if mode := fi.Mode(); mode.IsDir() { - if len(request.Path) == 0 || request.Path[len(request.Path)-1] != '/' { - // Add trailing slash - writeHeader(c, statusRedirectPermanent, request.String()+"/") - return - } - - _, err := os.Stat(path.Join(filePath, "index.gmi")) - if err != nil { - _, err := os.Stat(path.Join(filePath, "index.gemini")) - if err != nil { - if listDir { - serveDirList(c, request, filePath) - return - } - writeStatus(c, statusNotFound) - return - } - filePath = path.Join(filePath, "index.gemini") - } else { - filePath = path.Join(filePath, "index.gmi") - } - } - +func serveFile(c net.Conn, filePath string) { // Open file file, _ := os.Open(filePath) defer file.Close() diff --git a/server_proxy.go b/serve_proxy.go similarity index 100% rename from server_proxy.go rename to serve_proxy.go diff --git a/server.go b/server.go index 5ca0581..372bc0f 100644 --- a/server.go +++ b/server.go @@ -9,6 +9,7 @@ import ( "log" "net" "net/url" + "os" "path" "regexp" "strconv" @@ -105,24 +106,42 @@ func servePath(c *tls.Conn, request *url.URL, serve *pathConfig) { resolvedPath := request.Path requestSplit := strings.Split(request.Path, "/") pathSlashes := len(slashesRegexp.FindAllStringIndex(serve.Path, -1)) - if len(request.Path) > 0 && request.Path[0] == '/' { - pathSlashes++ // Regexp does not match starting slash + if len(serve.Path) > 0 { + if serve.Path[0] == '/' { + pathSlashes++ // Regexp does not match starting slash + } + if serve.Path[len(serve.Path)-1] != '/' { + pathSlashes++ + } } - if len(requestSplit) >= pathSlashes+1 { - resolvedPath = "/" + strings.Join(requestSplit[pathSlashes+1:], "/") + if len(requestSplit) >= pathSlashes { + resolvedPath = strings.Join(requestSplit[pathSlashes:], "/") + } + + var filePath string + if serve.Root != "" { + root := serve.Root + if root[len(root)-1] != '/' { + root += "/" + } + filePath = path.Join(root, resolvedPath) } if serve.Proxy != "" { serveProxy(c, request, serve.Proxy) return } else if serve.FastCGI != "" { + if filePath == "" { + writeStatus(c, statusNotFound) + return + } + contentType := "text/gemini; charset=utf-8" if serve.Type != "" { contentType = serve.Type } writeHeader(c, statusSuccess, contentType) - filePath := path.Join(serve.Root, request.Path[1:]) serveFastCGI(c, config.fcgiPools[serve.FastCGI], request, filePath) return } else if serve.cmd != nil { @@ -137,11 +156,48 @@ func servePath(c *tls.Conn, request *url.URL, serve *pathConfig) { serveCommand(c, request, serve.cmd) return } - filePath := resolvedPath - if len(filePath) > 0 && filePath[0] == '/' { - filePath = filePath[1:] + + if filePath == "" { + writeStatus(c, statusNotFound) + return } - serveFile(c, request, path.Join(serve.Root, filePath), serve.ListDirectory) + + fi, err := os.Stat(filePath) + if err != nil { + writeStatus(c, statusNotFound) + return + } + + mode := fi.Mode() + hasTrailingSlash := len(request.Path) > 0 && request.Path[len(request.Path)-1] == '/' + if mode.IsDir() { + if !hasTrailingSlash { + writeHeader(c, statusRedirectPermanent, request.String()+"/") + return + } + + _, err := os.Stat(path.Join(filePath, "index.gmi")) + if err != nil { + _, err := os.Stat(path.Join(filePath, "index.gemini")) + if err != nil { + if serve.ListDirectory { + serveDirList(c, request, filePath) + return + } + writeStatus(c, statusNotFound) + return + } + filePath = path.Join(filePath, "index.gemini") + } else { + filePath = path.Join(filePath, "index.gmi") + } + } else if hasTrailingSlash && len(request.Path) > 1 { + r := request.String() + writeHeader(c, statusRedirectPermanent, r[:len(r)-1]) + return + } + + serveFile(c, filePath) } func serveConn(c *tls.Conn) {