💾 Archived View for source.community › ckaznocha › gemini › raw › main › url.go captured on 2021-12-17 at 13:26:06.

View Raw

More Information

-=-=-=-=-=-=-

package gemini

import (
	"fmt"
	"net/url"
	"unicode/utf8"
)

// URI represents a Gemini URI.
//
// Resources hosted via Gemini are identified using URIs with the scheme
// "gemini".  This scheme is syntactically compatible with the generic URI
// syntax defined in RFC 3986, but does not support all components of the
// generic syntax.  In particular, the authority component is allowed and
// required, but its userinfo subcomponent is NOT allowed.  The host
// subcomponent is required.  The port subcomponent is optional, with a default
// value of 1965.  The path, query and fragment components are allowed and have
// no special meanings beyond those defined by the generic syntax.  Spaces in
// gemini URIs should be encoded as %20, not +.
type URI struct {
	url *url.URL

	Host     string
	Port     string
	Path     string
	Fragment string

	// Query is the decoded query section. The original query is stored in
	// RawQuery.
	Query string

	// RawQuery is the original query section as received by the server. Use
	// this in the event you need to parse a query section into a url.Values.
	RawQuery string
}

// ParseRequestURI parses a raw URI string into a Gemini URI. It returns an error if
// the URI is invalid.
func ParseRequestURI(rawURI string) (*URI, error) {
	if !utf8.ValidString(rawURI) {
		return nil, fmt.Errorf("%w: request URI must be valid utf-8", ErrMalformedURI)
	}

	uri, err := url.ParseRequestURI(rawURI)
	if err != nil {
		return nil, fmt.Errorf("%w: %s", ErrMalformedURI, err)
	}

	if !uri.IsAbs() {
		return nil, fmt.Errorf("%w: request URI cannot be relative", ErrMalformedURI)
	}

	if uri.User != nil {
		return nil, fmt.Errorf("%w: URI cannot have a user", ErrMalformedURI)
	}

	if uri.Host == "" {
		return nil, fmt.Errorf("%w: URI must have a host", ErrMalformedURI)
	}

	query, err := url.PathUnescape(uri.RawQuery)
	if err != nil {
		return nil, fmt.Errorf("%w: URI has a malformed query section: %s", ErrMalformedURI, err)
	}

	return &URI{
		Host:     uri.Hostname(),
		Port:     uri.Port(),
		Path:     uri.Path,
		Fragment: uri.Fragment,
		Query:    query,
		RawQuery: uri.RawQuery,
		url:      uri,
	}, nil
}

func (u *URI) String() string {
	if u.url == nil {
		u.url = &url.URL{
			Scheme:   "gemini",
			Host:     u.Host,
			Path:     u.Path,
			RawQuery: u.RawQuery,
			Fragment: u.Fragment,
		}
	}

	return u.url.String()
}