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
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

View File

@ -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 != "" {

View File

@ -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()

View File

@ -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) {