|
|
|
@ -12,13 +12,20 @@ import (
|
|
|
|
|
"os" |
|
|
|
|
"path" |
|
|
|
|
"strings" |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
"github.com/h2non/filetype" |
|
|
|
|
"github.com/makeworld-the-better-one/go-gemini" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
const readTimeout = 30 * time.Second |
|
|
|
|
|
|
|
|
|
func writeHeader(c net.Conn, code int, meta string) { |
|
|
|
|
fmt.Fprintf(c, "%d %s\r\n", code, meta) |
|
|
|
|
|
|
|
|
|
if verbose { |
|
|
|
|
log.Printf("< %d %s\n", code, meta) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func writeStatus(c net.Conn, code int) { |
|
|
|
@ -26,6 +33,8 @@ func writeStatus(c net.Conn, code int) {
|
|
|
|
|
switch code { |
|
|
|
|
case gemini.StatusTemporaryFailure: |
|
|
|
|
meta = "Temporary failure" |
|
|
|
|
case gemini.StatusProxyError: |
|
|
|
|
meta = "Proxy error" |
|
|
|
|
case gemini.StatusBadRequest: |
|
|
|
|
meta = "Bad request" |
|
|
|
|
case gemini.StatusNotFound: |
|
|
|
@ -34,6 +43,35 @@ func writeStatus(c net.Conn, code int) {
|
|
|
|
|
writeHeader(c, code, meta) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func serveProxy(c net.Conn, proxyURL, requestData string) { |
|
|
|
|
original := proxyURL |
|
|
|
|
|
|
|
|
|
tlsConfig := &tls.Config{} |
|
|
|
|
if strings.HasPrefix(proxyURL, "gemini://") { |
|
|
|
|
proxyURL = proxyURL[9:] |
|
|
|
|
} else if strings.HasPrefix(proxyURL, "gemini-insecure://") { |
|
|
|
|
proxyURL = proxyURL[18:] |
|
|
|
|
tlsConfig.InsecureSkipVerify = true |
|
|
|
|
} |
|
|
|
|
proxy, err := tls.Dial("tcp", proxyURL, tlsConfig) |
|
|
|
|
if err != nil { |
|
|
|
|
writeStatus(c, gemini.StatusProxyError) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
defer proxy.Close() |
|
|
|
|
|
|
|
|
|
// Forward request
|
|
|
|
|
proxy.Write([]byte(requestData)) |
|
|
|
|
proxy.Write([]byte("\r\n")) |
|
|
|
|
|
|
|
|
|
// Forward response
|
|
|
|
|
io.Copy(c, proxy) |
|
|
|
|
|
|
|
|
|
if verbose { |
|
|
|
|
log.Printf("< %s\n", original) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func serveFile(c net.Conn, filePath string) { |
|
|
|
|
fi, err := os.Stat(filePath) |
|
|
|
|
if os.IsNotExist(err) { |
|
|
|
@ -111,6 +149,8 @@ func scanCRLF(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
|
|
|
|
func handleConn(c net.Conn) { |
|
|
|
|
defer c.Close() |
|
|
|
|
|
|
|
|
|
c.SetReadDeadline(time.Now().Add(readTimeout)) |
|
|
|
|
|
|
|
|
|
var requestData string |
|
|
|
|
scanner := bufio.NewScanner(c) |
|
|
|
|
scanner.Split(scanCRLF) |
|
|
|
@ -135,19 +175,28 @@ func handleConn(c net.Conn) {
|
|
|
|
|
if strippedPath[0] == '/' { |
|
|
|
|
strippedPath = strippedPath[1:] |
|
|
|
|
} |
|
|
|
|
if verbose { |
|
|
|
|
log.Printf("> %s\n", request) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var realPath string |
|
|
|
|
for _, serve := range config.Serve { |
|
|
|
|
if serve.Proxy != "" { |
|
|
|
|
if serve.r != nil && serve.r.Match(pathBytes) { |
|
|
|
|
serveProxy(c, serve.Proxy, requestData) |
|
|
|
|
return |
|
|
|
|
} else if serve.r == nil && strings.HasPrefix(request.Path, serve.Path) { |
|
|
|
|
serveProxy(c, serve.Proxy, requestData) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if serve.r != nil && serve.r.Match(pathBytes) { |
|
|
|
|
realPath = path.Join(serve.Root, strippedPath) |
|
|
|
|
serveFile(c, path.Join(serve.Root, strippedPath)) |
|
|
|
|
return |
|
|
|
|
} else if serve.r == nil && strings.HasPrefix(request.Path, serve.Path) { |
|
|
|
|
realPath = path.Join(serve.Root, request.Path[len(serve.Path):]) |
|
|
|
|
} else { |
|
|
|
|
continue |
|
|
|
|
serveFile(c, path.Join(serve.Root, strippedPath[len(serve.Path)-1:])) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
serveFile(c, realPath) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
writeStatus(c, gemini.StatusNotFound) |
|
|
|
|