Better error handling, many fixes all around
This commit is contained in:
@@ -4,15 +4,18 @@ import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gemini-grc/logging"
|
||||
"io"
|
||||
"net"
|
||||
gourl "net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gemini-grc/config"
|
||||
|
||||
"github.com/guregu/null/v5"
|
||||
)
|
||||
|
||||
@@ -55,8 +58,7 @@ func ConnectAndGetData(url string) ([]byte, error) {
|
||||
host := fmt.Sprintf("%s:%s", hostname, port)
|
||||
// Establish the underlying TCP connection.
|
||||
dialer := &net.Dialer{
|
||||
Timeout: time.Duration(config.CONFIG.ResponseTimeout) * time.Second,
|
||||
KeepAlive: 10 * time.Second,
|
||||
Timeout: time.Duration(config.CONFIG.ResponseTimeout) * time.Second,
|
||||
}
|
||||
conn, err := dialer.Dial("tcp", host)
|
||||
if err != nil {
|
||||
@@ -95,7 +97,11 @@ func ConnectAndGetData(url string) ([]byte, error) {
|
||||
var data []byte
|
||||
|
||||
// Send Gemini request to trigger server response.
|
||||
_, err = tlsConn.Write([]byte(fmt.Sprintf("%s\r\n", url)))
|
||||
// Fix for stupid server bug:
|
||||
// Some servers return 'Header: 53 No proxying to other hosts or ports!'
|
||||
// when the port is 1965 and is still specified explicitely in the URL.
|
||||
_url, _ := ParseURL(url, "")
|
||||
_, err = tlsConn.Write([]byte(fmt.Sprintf("%s\r\n", _url.StringNoDefaultPort())))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrNetworkCannotWrite, err)
|
||||
}
|
||||
@@ -111,9 +117,8 @@ func ConnectAndGetData(url string) ([]byte, error) {
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
} else {
|
||||
return nil, fmt.Errorf("%w: %w", ErrNetwork, err)
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %w", ErrNetwork, err)
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
@@ -121,7 +126,19 @@ func ConnectAndGetData(url string) ([]byte, error) {
|
||||
|
||||
// Visit given URL, using the Gemini protocol.
|
||||
// Mutates given Snapshot with the data.
|
||||
func Visit(s *Snapshot) error {
|
||||
// In case of error, we store the error string
|
||||
// inside snapshot and return the error.
|
||||
func Visit(s *Snapshot) (err error) {
|
||||
// Don't forget to also store error
|
||||
// response code (if we have one)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
s.Error = null.StringFrom(err.Error())
|
||||
if errors.As(err, new(*GeminiError)) {
|
||||
s.ResponseCode = null.IntFrom(int64(err.(*GeminiError).Code))
|
||||
}
|
||||
}
|
||||
}()
|
||||
data, err := ConnectAndGetData(s.URL.String())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -130,6 +147,9 @@ func Visit(s *Snapshot) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//marshalled, _ := json.MarshalIndent(pageData, "", " ")
|
||||
//fmt.Printf("%s\n", marshalled)
|
||||
s.Header = null.StringFrom(pageData.ResponseHeader)
|
||||
s.ResponseCode = null.IntFrom(int64(pageData.ResponseCode))
|
||||
s.MimeType = null.StringFrom(pageData.MimeType)
|
||||
s.Lang = null.StringFrom(pageData.Lang)
|
||||
@@ -142,24 +162,21 @@ func Visit(s *Snapshot) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update given snapshot with the
|
||||
// Gemini header data: response code,
|
||||
// mime type and lang (optional)
|
||||
// processData returne results from
|
||||
// parsing Gemini header data:
|
||||
// Code, mime type and lang (optional)
|
||||
// Returns error if header was invalid
|
||||
func processData(data []byte) (*PageData, error) {
|
||||
header, body, err := getHeadersAndData(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
code, mimeType, lang := getMimeTypeAndLang(header)
|
||||
var geminiError error
|
||||
logging.LogDebug("Header: %s", strings.TrimSpace(header))
|
||||
if code != 20 {
|
||||
geminiError = NewErrGeminiStatusCode(code, header)
|
||||
return nil, NewErrGeminiStatusCode(code, header)
|
||||
}
|
||||
fmt.Printf("%v\n", header)
|
||||
|
||||
if geminiError != nil {
|
||||
return nil, geminiError
|
||||
}
|
||||
pageData := PageData{
|
||||
ResponseCode: code,
|
||||
ResponseHeader: header,
|
||||
@@ -201,11 +218,11 @@ func getHeadersAndData(data []byte) (string, []byte, error) {
|
||||
// `20 text/gemini` (code, mimetype)
|
||||
// `31 gemini://redirected.to/other/site` (code)
|
||||
func getMimeTypeAndLang(headers string) (int, string, string) {
|
||||
// Regex that parses code, mimetype & lang
|
||||
re := regexp.MustCompile(`^(\d+)\s+([a-zA-Z0-9/\-+]+)(?:[;\s]+(lang=([a-zA-Z0-9-]+)))?\s*$`)
|
||||
// Regex that parses code, mimetype & optional charset/lang parameters
|
||||
re := regexp.MustCompile(`^(\d+)\s+([a-zA-Z0-9/\-+]+)(?:[;\s]+(?:(?:charset|lang)=([a-zA-Z0-9-]+)))?\s*$`)
|
||||
matches := re.FindStringSubmatch(headers)
|
||||
if matches == nil || len(matches) <= 1 {
|
||||
// Try to get code at least.
|
||||
// Try to get code at least
|
||||
re := regexp.MustCompile(`^(\d+)\s+`)
|
||||
matches := re.FindStringSubmatch(headers)
|
||||
if matches == nil || len(matches) <= 1 {
|
||||
@@ -222,6 +239,6 @@ func getMimeTypeAndLang(headers string) (int, string, string) {
|
||||
return 0, "", ""
|
||||
}
|
||||
mimeType := matches[2]
|
||||
lang := matches[4]
|
||||
return code, mimeType, lang
|
||||
param := matches[3] // This will capture either charset or lang value
|
||||
return code, mimeType, param
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user