Improvements in error handling & descriptions

This commit is contained in:
2025-02-27 09:20:22 +02:00
parent 9dc008cb0f
commit efaedcc6b2
5 changed files with 32 additions and 15 deletions

9
common/text/text.go Normal file
View File

@@ -0,0 +1,9 @@
package text
import "strings"
// RemoveNullChars removes all null characters from the input string.
func RemoveNullChars(input string) string {
// Replace all null characters with an empty string
return strings.ReplaceAll(input, "\u0000", "")
}

View File

@@ -20,21 +20,29 @@ func (e *GeminiError) Error() string {
return fmt.Sprintf("gemini error: code %d %s", e.Code, e.Msg) return fmt.Sprintf("gemini error: code %d %s", e.Code, e.Msg)
} }
// NewGeminiError creates a new GeminiError based on the status code and header.
// Status codes are based on the Gemini protocol specification:
// - 1x: Input required
// - 2x: Success (not handled as errors)
// - 3x: Redirect
// - 4x: Temporary failure
// - 5x: Permanent failure
// - 6x: Client certificate required/rejected
func NewGeminiError(code int, header string) error { func NewGeminiError(code int, header string) error {
var msg string var msg string
switch { switch {
case code >= 10 && code < 20: case code >= 10 && code < 20:
msg = "needs input" msg = fmt.Sprintf("input required: %s", header)
case code >= 30 && code < 40: case code >= 30 && code < 40:
msg = "redirect" msg = fmt.Sprintf("redirect: %s", header)
case code >= 40 && code < 50: case code >= 40 && code < 50:
msg = "bad request" msg = fmt.Sprintf("request failed: %s", header)
case code >= 50 && code < 60: case code >= 50 && code < 60:
msg = "server error" msg = fmt.Sprintf("server error: %s", header)
case code >= 60 && code < 70: case code >= 60 && code < 70:
msg = "TLS error" msg = fmt.Sprintf("TLS error: %s", header)
default: default:
msg = "unexpected Status code" msg = fmt.Sprintf("unexpected status code %d: %s", code, header)
} }
return &GeminiError{ return &GeminiError{
Msg: msg, Msg: msg,
@@ -43,6 +51,7 @@ func NewGeminiError(code int, header string) error {
} }
} }
// IsGeminiError checks if the given error is a GeminiError.
func IsGeminiError(err error) bool { func IsGeminiError(err error) bool {
if err == nil { if err == nil {
return false return false

View File

@@ -17,6 +17,7 @@ import (
_url "gemini-grc/common/url" _url "gemini-grc/common/url"
"gemini-grc/config" "gemini-grc/config"
"gemini-grc/logging" "gemini-grc/logging"
"github.com/antanst/go_errors" "github.com/antanst/go_errors"
"github.com/guregu/null/v5" "github.com/guregu/null/v5"
) )
@@ -182,7 +183,7 @@ func processData(s snapshot.Snapshot, data []byte) (*snapshot.Snapshot, error) {
if mimeType == "text/gemini" { if mimeType == "text/gemini" {
validBody, err := BytesToValidUTF8(body) validBody, err := BytesToValidUTF8(body)
if err != nil { if err != nil {
return nil, go_errors.NewError(err) return nil, err
} }
s.GemText = null.StringFrom(validBody) s.GemText = null.StringFrom(validBody)
} else { } else {

View File

@@ -7,6 +7,7 @@ import (
"io" "io"
"unicode/utf8" "unicode/utf8"
"github.com/antanst/go_errors"
"golang.org/x/text/encoding/charmap" "golang.org/x/text/encoding/charmap"
"golang.org/x/text/encoding/japanese" "golang.org/x/text/encoding/japanese"
"golang.org/x/text/encoding/korean" "golang.org/x/text/encoding/korean"
@@ -24,7 +25,7 @@ func BytesToValidUTF8(input []byte) (string, error) {
} }
const maxSize = 10 * 1024 * 1024 // 10MB const maxSize = 10 * 1024 * 1024 // 10MB
if len(input) > maxSize { if len(input) > maxSize {
return "", fmt.Errorf("%w: %d bytes (max %d)", ErrInputTooLarge, len(input), maxSize) return "", go_errors.NewError(fmt.Errorf("%w: %d bytes (max %d)", ErrInputTooLarge, len(input), maxSize))
} }
// remove NULL byte 0x00 (ReplaceAll accepts slices) // remove NULL byte 0x00 (ReplaceAll accepts slices)
inputNoNull := bytes.ReplaceAll(input, []byte{byte(0)}, []byte{}) inputNoNull := bytes.ReplaceAll(input, []byte{byte(0)}, []byte{})
@@ -55,5 +56,5 @@ func BytesToValidUTF8(input []byte) (string, error) {
} }
} }
return "", fmt.Errorf("%w (tried %d encodings): %w", ErrUTF8Conversion, len(encodings), lastErr) return "", go_errors.NewError(fmt.Errorf("%w (tried %d encodings): %w", ErrUTF8Conversion, len(encodings), lastErr))
} }

View File

@@ -13,9 +13,11 @@ import (
errors2 "gemini-grc/common/errors" errors2 "gemini-grc/common/errors"
"gemini-grc/common/linkList" "gemini-grc/common/linkList"
"gemini-grc/common/snapshot" "gemini-grc/common/snapshot"
"gemini-grc/common/text"
_url "gemini-grc/common/url" _url "gemini-grc/common/url"
"gemini-grc/config" "gemini-grc/config"
"gemini-grc/logging" "gemini-grc/logging"
"github.com/antanst/go_errors" "github.com/antanst/go_errors"
"github.com/guregu/null/v5" "github.com/guregu/null/v5"
) )
@@ -85,7 +87,7 @@ func Visit(url string) (*snapshot.Snapshot, error) {
isValidUTF8 := utf8.ValidString(string(data)) isValidUTF8 := utf8.ValidString(string(data))
if isValidUTF8 { if isValidUTF8 {
s.GemText = null.StringFrom(removeNullChars(string(data))) s.GemText = null.StringFrom(text.RemoveNullChars(string(data)))
} else { } else {
s.Data = null.ValueFrom(data) s.Data = null.ValueFrom(data)
} }
@@ -276,8 +278,3 @@ func getGopherPageLinks(content string) []string {
return links return links
} }
func removeNullChars(input string) string {
// Replace all null characters with an empty string
return strings.ReplaceAll(input, "\u0000", "")
}