package utilities import ( "errors" "fmt" "net" "net/url" "regexp" ) const ( httpScheme = "http://" httpsScheme = "https://" ) var ( ErrMissingHostname = errors.New("the hostname is missing from the URL") ErrHostIsIPAddress = errors.New("the hostname is an IP address") ErrInvalidURLScheme = errors.New("invalid URL scheme") ErrURLContainsFragment = errors.New("the URL contains a fragment") ErrURLContainsPort = errors.New("the URL contains a port") ) // ValidateProfileURL validates the given profile URL according to the indieauth // specification. ValidateProfileURL returns the canonicalised profile URL after // validation checks. func ValidateProfileURL(profileURL string) (string, error) { // Using regex to get and validate the scheme. // If its missing then set the scheme to https pattern := regexp.MustCompile(`^[a-z].*:\/\/|^[a-z].*:`) scheme := pattern.FindString(profileURL) if scheme == "" { profileURL = httpsScheme + profileURL } else if scheme != httpsScheme && scheme != httpScheme { return "", ErrInvalidURLScheme } parsedProfileURL, err := url.Parse(profileURL) if err != nil { return "", fmt.Errorf("unable to parse the URL %q: %w", profileURL, err) } if parsedProfileURL.Hostname() == "" { return "", ErrMissingHostname } if ip := net.ParseIP(parsedProfileURL.Hostname()); ip != nil { return "", ErrHostIsIPAddress } if parsedProfileURL.Fragment != "" { return "", ErrURLContainsFragment } if parsedProfileURL.Port() != "" { return "", ErrURLContainsPort } if parsedProfileURL.Scheme == "" { parsedProfileURL.Scheme = "https" } if parsedProfileURL.Path == "" { parsedProfileURL.Path = "/" } return parsedProfileURL.String(), nil }