116 lines
2.2 KiB
Go
116 lines
2.2 KiB
Go
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
|
|
}
|