twink/vendor/sourcery.dny.nu/longdistance/internal/url/url.go

117 lines
2.2 KiB
Go
Raw Normal View History

2025-12-05 12:20:05 +01:00
package url
import (
"fmt"
"net/url"
"path"
"slices"
"strings"
)
var Parse = url.Parse
func Relative(base string, iri string) (string, error) {
baseURL, err := Parse(base)
if err != nil {
return "", fmt.Errorf("failed to parse base URL: %w", err)
}
absURL, err := Parse(iri)
if err != nil {
return "", fmt.Errorf("failed to parse absolute URL: %w", err)
}
if baseURL.Scheme != absURL.Scheme || baseURL.Host != absURL.Host {
return "", fmt.Errorf("cannot create relative URL when host or scheme differ")
}
basePath := baseURL.EscapedPath()
absPath := absURL.EscapedPath()
if basePath == absPath {
if absURL.Fragment != "" || absURL.RawQuery != "" {
return (&url.URL{
RawQuery: absURL.RawQuery,
Fragment: absURL.Fragment,
}).String(), nil
}
}
last := strings.LastIndex(basePath, "/")
basePath = basePath[:last+1]
baseParts := strings.Split(basePath, "/")
absParts := strings.Split(absPath, "/")
prefix := 0
lap := len(absParts)
count := min(len(baseParts), lap)
for i, elem := range baseParts[:count] {
if elem == absParts[i] {
prefix++
} else {
break
}
}
relpaths := make([]string, 0, len(baseParts)-prefix)
for range baseParts[prefix+1:] {
relpaths = append(relpaths, "..")
}
relpaths = append(relpaths, absParts[prefix:]...)
final := path.Join(relpaths...)
// Include query and fragment if present
relURL := &url.URL{
Path: final,
RawQuery: absURL.RawQuery,
Fragment: absURL.Fragment,
}
res := relURL.String()
if strings.HasSuffix(res, "..") {
res = res + "/"
}
return res, nil
}
func EndsInGenDelim(s string) bool {
delims := []string{":", "/", "?", "#", "[", "]", "@"}
last := s[len(s)-1:]
return slices.Contains(delims, last)
}
func IsRelative(s string) bool {
_, err := Parse(s)
return err == nil
}
func IsIRI(s string) bool {
u, err := Parse(s)
if err != nil {
return false
}
ns := u.String()
if strings.HasSuffix(s, "#") {
// preserve the empty fragment
ns = ns + "#"
}
return u.IsAbs() && s == ns
}
func Resolve(base string, val string) (string, error) {
r, err := Parse(val)
if err != nil {
return "", err
}
u, err := Parse(base)
if err != nil {
return "", err
}
return u.ResolveReference(r).String(), nil
}