Redirect requests with invalid trailing slash

This commit is contained in:
Trevor Slocum 2020-11-05 10:00:32 -08:00
parent 2b7e21666b
commit 0b744eba7e
7 changed files with 78 additions and 63 deletions

View file

@ -65,7 +65,7 @@ and the private key file at `certs/live/$DOMAIN/privkey.pem` to twins.
### DisableSize ### DisableSize
The size of the response body is included in the media type header by default. 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 ### Path

View file

@ -126,8 +126,6 @@ func readconfig(configPath string) error {
if serve.Path[0] == '^' { if serve.Path[0] == '^' {
serve.r = regexp.MustCompile(serve.Path) 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 != "" { if serve.FastCGI != "" {

View file

@ -6,7 +6,6 @@ import (
"net" "net"
"net/url" "net/url"
"os" "os"
"path"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
@ -54,26 +53,18 @@ func serveDirList(c net.Conn, request *url.URL, dirPath string) {
writeHeader(c, statusSuccess, "text/gemini; charset=utf-8") writeHeader(c, statusSuccess, "text/gemini; charset=utf-8")
fmt.Fprintf(c, "# %s\r\n", request.Path) fmt.Fprintf(c, "# %s\r\n", request.Path)
if numDirs > 0 || numFiles > 0 { if numDirs == 1 {
if numDirs > 0 { c.Write([]byte("1 directory"))
if numDirs == 1 { } else {
c.Write([]byte("1 directory")) fmt.Fprintf(c, "%d directories", numDirs)
} 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"))
} }
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 != "/" { if request.Path != "/" {
c.Write([]byte("=> ../ ../\r\n\r\n")) 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) { func serveFile(c net.Conn, filePath string) {
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")
}
}
// Open file // Open file
file, _ := os.Open(filePath) file, _ := os.Open(filePath)
defer file.Close() defer file.Close()

View file

@ -9,6 +9,7 @@ import (
"log" "log"
"net" "net"
"net/url" "net/url"
"os"
"path" "path"
"regexp" "regexp"
"strconv" "strconv"
@ -105,24 +106,42 @@ func servePath(c *tls.Conn, request *url.URL, serve *pathConfig) {
resolvedPath := request.Path resolvedPath := request.Path
requestSplit := strings.Split(request.Path, "/") requestSplit := strings.Split(request.Path, "/")
pathSlashes := len(slashesRegexp.FindAllStringIndex(serve.Path, -1)) pathSlashes := len(slashesRegexp.FindAllStringIndex(serve.Path, -1))
if len(request.Path) > 0 && request.Path[0] == '/' { if len(serve.Path) > 0 {
pathSlashes++ // Regexp does not match starting slash if serve.Path[0] == '/' {
pathSlashes++ // Regexp does not match starting slash
}
if serve.Path[len(serve.Path)-1] != '/' {
pathSlashes++
}
} }
if len(requestSplit) >= pathSlashes+1 { if len(requestSplit) >= pathSlashes {
resolvedPath = "/" + strings.Join(requestSplit[pathSlashes+1:], "/") 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 != "" { if serve.Proxy != "" {
serveProxy(c, request, serve.Proxy) serveProxy(c, request, serve.Proxy)
return return
} else if serve.FastCGI != "" { } else if serve.FastCGI != "" {
if filePath == "" {
writeStatus(c, statusNotFound)
return
}
contentType := "text/gemini; charset=utf-8" contentType := "text/gemini; charset=utf-8"
if serve.Type != "" { if serve.Type != "" {
contentType = serve.Type contentType = serve.Type
} }
writeHeader(c, statusSuccess, contentType) writeHeader(c, statusSuccess, contentType)
filePath := path.Join(serve.Root, request.Path[1:])
serveFastCGI(c, config.fcgiPools[serve.FastCGI], request, filePath) serveFastCGI(c, config.fcgiPools[serve.FastCGI], request, filePath)
return return
} else if serve.cmd != nil { } else if serve.cmd != nil {
@ -137,11 +156,48 @@ func servePath(c *tls.Conn, request *url.URL, serve *pathConfig) {
serveCommand(c, request, serve.cmd) serveCommand(c, request, serve.cmd)
return return
} }
filePath := resolvedPath
if len(filePath) > 0 && filePath[0] == '/' { if filePath == "" {
filePath = filePath[1:] 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) { func serveConn(c *tls.Conn) {