Refactor error handling and logging system

- Replace custom errors package with xerrors for structured error handling
- Remove local logging wrapper and use git.antanst.com/antanst/logging
- Add proper error codes and user messages in server responses
- Improve connection handling with better error categorization
- Update certificate path to use local certs/ directory
- Add request size validation (1024 byte limit)
- Remove panic-on-error configuration option
- Enhance error logging with connection IDs and remote addresses
This commit is contained in:
2025-05-26 13:28:16 +03:00
parent c78d7898f9
commit 28008a320d
6 changed files with 108 additions and 283 deletions

View File

@@ -2,16 +2,19 @@ package server
import (
"crypto/tls"
"errors"
"fmt"
"net"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"gemserve/common"
"gemserve/config"
"gemserve/errors"
"gemserve/logging"
logging "git.antanst.com/antanst/logging"
"git.antanst.com/antanst/xerrors"
"github.com/gabriel-vasile/mimetype"
)
@@ -20,18 +23,43 @@ type ServerConfig interface {
RootPath() string
}
func checkRequestURL(url *common.URL) error {
if url.Protocol != "gemini" {
return xerrors.NewError(fmt.Errorf("invalid URL"), 53, "URL Protocol not Gemini, proxying refused", false)
}
_, portStr, err := net.SplitHostPort(config.CONFIG.Listen)
if err != nil {
return xerrors.NewError(fmt.Errorf("failed to parse listen address: %w", err), 50, "Server configuration error", false)
}
listenPort, err := strconv.Atoi(portStr)
if err != nil {
return xerrors.NewError(fmt.Errorf("failed to parse listen port: %w", err), 50, "Server configuration error", false)
}
if url.Port != listenPort {
return xerrors.NewError(fmt.Errorf("failed to parse URL: %w", err), 53, "invalid URL port, proxying refused", false)
}
return nil
}
func GenerateResponse(conn *tls.Conn, connId string, input string) ([]byte, error) {
trimmedInput := strings.TrimSpace(input)
// url will have a cleaned and normalized path after this
url, err := common.ParseURL(trimmedInput, "", true)
if err != nil {
return nil, errors.NewConnectionError(fmt.Errorf("failed to parse URL: %w", err))
return nil, xerrors.NewError(fmt.Errorf("failed to parse URL: %w", err), 59, "Invalid URL", false)
}
logging.LogDebug("%s %s normalized URL path: %s", connId, conn.RemoteAddr(), url.Path)
err = checkRequestURL(url)
if err != nil {
return nil, err
}
serverRootPath := config.CONFIG.RootPath
localPath, err := calculateLocalPath(url.Path, serverRootPath)
if err != nil {
return nil, errors.NewConnectionError(err)
return nil, xerrors.NewError(err, 59, "Invalid path", false)
}
logging.LogDebug("%s %s request file path: %s", connId, conn.RemoteAddr(), localPath)
@@ -40,7 +68,7 @@ func GenerateResponse(conn *tls.Conn, connId string, input string) ([]byte, erro
if errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrPermission) {
return []byte("51 not found\r\n"), nil
} else if err != nil {
return nil, errors.NewConnectionError(fmt.Errorf("%s %s failed to access path: %w", connId, conn.RemoteAddr(), err))
return nil, xerrors.NewError(fmt.Errorf("%s %s failed to access path: %w", connId, conn.RemoteAddr(), err), 0, "Path access failed", false)
}
// Handle directory.
@@ -55,7 +83,7 @@ func generateResponseFile(conn *tls.Conn, connId string, url *common.URL, localP
if errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrPermission) {
return []byte("51 not found\r\n"), nil
} else if err != nil {
return nil, errors.NewConnectionError(fmt.Errorf("%s %s failed to read file: %w", connId, conn.RemoteAddr(), err))
return nil, xerrors.NewError(fmt.Errorf("%s %s failed to read file: %w", connId, conn.RemoteAddr(), err), 0, "File read failed", false)
}
var mimeType string
@@ -64,7 +92,7 @@ func generateResponseFile(conn *tls.Conn, connId string, url *common.URL, localP
} else {
mimeType = mimetype.Detect(data).String()
}
headerBytes := []byte(fmt.Sprintf("20 %s\r\n", mimeType))
headerBytes := []byte(fmt.Sprintf("20 %s; lang=en\r\n", mimeType))
response := append(headerBytes, data...)
return response, nil
}
@@ -72,7 +100,7 @@ func generateResponseFile(conn *tls.Conn, connId string, url *common.URL, localP
func generateResponseDir(conn *tls.Conn, connId string, url *common.URL, localPath string) (output []byte, err error) {
entries, err := os.ReadDir(localPath)
if err != nil {
return nil, errors.NewConnectionError(fmt.Errorf("%s %s failed to read directory: %w", connId, conn.RemoteAddr(), err))
return nil, xerrors.NewError(fmt.Errorf("%s %s failed to read directory: %w", connId, conn.RemoteAddr(), err), 0, "Directory read failed", false)
}
if config.CONFIG.DirIndexingEnabled {
@@ -87,7 +115,7 @@ func generateResponseDir(conn *tls.Conn, connId string, url *common.URL, localPa
}
}
data := []byte(strings.Join(contents, ""))
headerBytes := []byte("20 text/gemini;\r\n")
headerBytes := []byte("20 text/gemini; lang=en\r\n")
response := append(headerBytes, data...)
return response, nil
} else {
@@ -100,7 +128,7 @@ func generateResponseDir(conn *tls.Conn, connId string, url *common.URL, localPa
func calculateLocalPath(input string, basePath string) (string, error) {
// Check for invalid characters early
if strings.ContainsAny(input, "\\") {
return "", errors.NewError(fmt.Errorf("invalid characters in path: %s", input))
return "", xerrors.NewError(fmt.Errorf("invalid characters in path: %s", input), 0, "Invalid path characters", false)
}
// If IsLocal(path) returns true, then Join(base, path)
@@ -116,7 +144,7 @@ func calculateLocalPath(input string, basePath string) (string, error) {
localPath, err := filepath.Localize(filePath)
if err != nil || !filepath.IsLocal(localPath) {
return "", errors.NewError(fmt.Errorf("could not construct local path from %s: %s", input, err))
return "", xerrors.NewError(fmt.Errorf("could not construct local path from %s: %s", input, err), 0, "Invalid local path", false)
}
filePath = path.Join(basePath, localPath)