Factor out header parsing code for CGI and SCGI into one function, so the checks from last commit can be applied to both.

This commit is contained in:
Solderpunk 2025-02-09 18:58:24 +01:00
parent 0f833ba57b
commit 565f54bff8

View file

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"context" "context"
"crypto/tls" "crypto/tls"
"errors"
"io" "io"
"log" "log"
"net" "net"
@ -15,6 +16,35 @@ import (
"time" "time"
) )
func extractStatusFromDynamicResponse(reader *bufio.Reader, source string) (int, error) {
rawHeader, _, err := reader.ReadLine()
if err != nil {
log.Println("Unable to read header in dynamic output from " + source + ".")
return 0, err
}
header := string(rawHeader)
logErrorMsg := "Unable to parse first line of dynamic output from " +
source +
" as valid Gemini response header. Line was: " +
header
header_fields := strings.Fields(header)
if len(header_fields) == 0 {
log.Println(logErrorMsg)
return 0, errors.New("")
}
status, err := strconv.Atoi(header_fields[0])
if err != nil {
log.Println(logErrorMsg)
return 0, err
}
if status < 10 || status > 70 {
log.Println(logErrorMsg)
return 0, errors.New("")
}
return status, nil
}
func handleCGI(config SysConfig, path string, cgiPath string, URL *url.URL, logEntry *LogEntry, conn net.Conn) { func handleCGI(config SysConfig, path string, cgiPath string, URL *url.URL, logEntry *LogEntry, conn net.Conn) {
// Find the shortest leading part of path which maps to an executable file. // Find the shortest leading part of path which maps to an executable file.
// Call this part scriptPath, and everything after it pathInfo. // Call this part scriptPath, and everything after it pathInfo.
@ -72,6 +102,7 @@ func handleCGI(config SysConfig, path string, cgiPath string, URL *url.URL, logE
logEntry.Status = 42 logEntry.Status = 42
return return
} }
// Extract response header // Extract response header
responseString := string(response) responseString := string(response)
if len(responseString) == 0 { if len(responseString) == 0 {
@ -80,28 +111,16 @@ func handleCGI(config SysConfig, path string, cgiPath string, URL *url.URL, logE
logEntry.Status = 42 logEntry.Status = 42
return return
} }
header, _, err := bufio.NewReader(strings.NewReader(string(response))).ReadLine()
if err != nil || len(header) == 0 { responseReader := bufio.NewReader(strings.NewReader(string(response)))
log.Println("Unable to parse first line of output from CGI process " + path + " as valid Gemini response header. Line was: " + string(header)) status, err := extractStatusFromDynamicResponse(responseReader, path)
if err != nil {
conn.Write([]byte("42 CGI error!\r\n")) conn.Write([]byte("42 CGI error!\r\n"))
logEntry.Status = 42 logEntry.Status = 42
return return
} else {
logEntry.Status = status
} }
header_fields := strings.Fields(string(header))
if len(header_fields) == 0 {
log.Println("Unable to parse first line of output from CGI process " + path + " as valid Gemini response header. Line was: " + string(header))
conn.Write([]byte("42 CGI error!\r\n"))
logEntry.Status = 42
return
}
status, err := strconv.Atoi(header_fields[0])
if err != nil || status < 10 || status > 70 {
log.Println("Unable to parse first line of output from CGI process " + path + " as valid Gemini response header. Line was: " + string(header))
conn.Write([]byte("42 CGI error!\r\n"))
logEntry.Status = 42
return
}
logEntry.Status = status
// Write response // Write response
conn.Write(response) conn.Write(response)
@ -135,36 +154,28 @@ func handleSCGI(URL *url.URL, scgiPath string, scgiSocket string, config SysConf
socket.Write([]byte(",")) socket.Write([]byte(","))
// Read and relay response // Read and relay response
responseReader := bufio.NewReader(socket)
status, err := extractStatusFromDynamicResponse(responseReader, scgiSocket)
if err != nil {
conn.Write([]byte("42 SCGI error!\r\n"))
logEntry.Status = 42
return
} else {
logEntry.Status = status
}
buffer := make([]byte, 1027) buffer := make([]byte, 1027)
first := true
for { for {
n, err := socket.Read(buffer) n, err := responseReader.Read(buffer)
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
break break
} else if !first { } else {
// Err // Err
log.Println("Error reading from SCGI socket " + scgiSocket + ": " + err.Error()) log.Println("Error reading from SCGI socket " + scgiSocket + ": " + err.Error())
conn.Write([]byte("42 Error reading from SCGI service!\r\n"))
logEntry.Status = 42
return
} else {
break break
} }
} }
// Extract status code from first line
if first {
first = false
lines := strings.SplitN(string(buffer), "\r\n", 2)
status, err := strconv.Atoi(strings.Fields(lines[0])[0])
if err != nil || status < 10 || status > 70 {
conn.Write([]byte("42 CGI error!\r\n"))
log.Println("Unable to parse first line of output from SCGI socket " + scgiSocket + " as valid Gemini response header. Line was: " + lines[0])
logEntry.Status = 42
return
}
logEntry.Status = status
}
// Send to client // Send to client
conn.Write(buffer[:n]) conn.Write(buffer[:n])
} }