package gemini import ( "database/sql/driver" "fmt" "gemini-grc/logging" "net/url" "path" "strconv" "strings" ) type URL struct { Protocol string `json:"protocol,omitempty"` Hostname string `json:"hostname,omitempty"` Port int `json:"port,omitempty"` Path string `json:"path,omitempty"` Descr string `json:"descr,omitempty"` Full string `json:"full,omitempty"` } func (u *URL) Scan(value interface{}) error { if value == nil { // Clear the fields in the current GeminiUrl object (not the pointer itself) *u = URL{} return nil } b, ok := value.(string) if !ok { return fmt.Errorf("failed to scan GeminiUrl: expected string, got %T", value) } parsedURL, err := ParseURL(b, "") if err != nil { return err } *u = *parsedURL return nil } func (u URL) String() string { return u.Full } func (u URL) StringNoDefaultPort() string { if u.Port == 1965 { return fmt.Sprintf("%s://%s%s", u.Protocol, u.Hostname, u.Path) } return u.Full } func (u URL) Value() (driver.Value, error) { if u.Full == "" { return nil, nil } return u.Full, nil } func ParseURL(input string, descr string) (*URL, error) { u, err := url.Parse(input) if err != nil { return nil, fmt.Errorf("%w: Input %s GeminiError %w", ErrURLParse, input, err) } protocol := u.Scheme hostname := u.Hostname() strPort := u.Port() path := u.Path if strPort == "" { strPort = "1965" } port, err := strconv.Atoi(strPort) if err != nil { return nil, fmt.Errorf("%w: Input %s GeminiError %w", ErrURLParse, input, err) } full := fmt.Sprintf("%s://%s:%d%s", protocol, hostname, port, path) return &URL{Protocol: protocol, Hostname: hostname, Port: port, Path: path, Descr: descr, Full: full}, nil } func DeriveAbsoluteURL(currentURL URL, input string) (*URL, error) { logging.LogDebug("Calculating redirect URL. Current %s header string %s", currentURL, input) // If URL is absolute, return just it if strings.Contains(input, "://") { return ParseURL(input, "") } // input is a path. Clean it and construct // new path var newPath string // Handle weird cases found in the wild if strings.HasPrefix(input, "/") { newPath = path.Clean(input) } else if input == "./" || input == "." { newPath = path.Join(currentURL.Path, "/") } else { newPath = path.Join(currentURL.Path, path.Clean(input)) } strURL := fmt.Sprintf("%s://%s:%d%s", currentURL.Protocol, currentURL.Hostname, currentURL.Port, newPath) return ParseURL(strURL, "") }