1// SPDX-License-Identifier: MIT
2
3// Package urlorigin gives the one canonical form of the origin of an http(s)
4// URL that the host and the executor both compare: the archive token of an
5// action goes only to the origin the host named, so the two sides must agree on
6// what "the same origin" means, whatever the spelling of the URL.
7package urlorigin
8
9import (
10 "net"
11 "net/url"
12 "strings"
13)
14
15// Canonical returns "scheme://host:port" for an absolute http or https URL: the
16// scheme and the host lower-cased, the default port (80, 443) filled in, an IPv6
17// host in brackets (net.JoinHostPort), so that "https://[::1]:8443/x" and
18// "https://[::1:8443]/x" (a different host, without a port) differ, and
19// "https://a.test" and "https://a.test:443" are the same. It returns "" for
20// anything else, including a URL with credentials, which never carries a token.
21func Canonical(raw string) string {
22 u, err := url.Parse(raw)
23 if err != nil || u.Host == "" || u.User != nil {
24 return ""
25 }
26 scheme := strings.ToLower(u.Scheme)
27 port, ok := effectivePort(scheme, u.Port())
28 if !ok || u.Hostname() == "" {
29 return ""
30 }
31 return scheme + "://" + net.JoinHostPort(strings.ToLower(u.Hostname()), port)
32}
33
34func effectivePort(scheme, port string) (string, bool) {
35 switch {
36 case port != "":
37 if scheme != "http" && scheme != "https" {
38 return "", false
39 }
40 return port, true
41 case scheme == "http":
42 return "80", true
43 case scheme == "https":
44 return "443", true
45 }
46 return "", false
47}
48
49// Prefix returns the origin as it is written at the start of a URL: the same
50// as Canonical without the default port, which is how a URL is built from it.
51func Prefix(raw string) string {
52 c := Canonical(raw)
53 switch {
54 case strings.HasPrefix(c, "http://") && strings.HasSuffix(c, ":80"):
55 return strings.TrimSuffix(c, ":80")
56 case strings.HasPrefix(c, "https://") && strings.HasSuffix(c, ":443"):
57 return strings.TrimSuffix(c, ":443")
58 }
59 return c
60}