Skip to content

Commit

Permalink
Add an Origin class.
Browse files Browse the repository at this point in the history
This works around golang/go#16142
However, it is fragile because it duplicates some private code from `net.http`, which could theoretically change its behaviour without notice.
  • Loading branch information
lgarron committed Jun 23, 2016
1 parent 9dd1b69 commit 4bd3657
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 0 deletions.
24 changes: 24 additions & 0 deletions origin/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package origin

import (
"net/url"
"strings"
)

// From https://golang.org/src/net/http/client.go
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }

// From https://golang.org/src/net/http/transport.go
var portMap = map[string]string{
"http": "80",
"https": "443",
}

// From https://golang.org/src/net/http/transport.go
func canonicalAddr(url *url.URL) string {
addr := url.Host
if !hasPort(addr) {
return addr + ":" + portMap[url.Scheme]
}
return addr
}
54 changes: 54 additions & 0 deletions origin/origin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package origin

import (
"net"
"net/url"
)

// Origin holds the explicit definition of an origin, based on the defintion
// in https://tools.ietf.org/html/rfc6454
type Origin struct {
// HostName is called HostName instead of Host to distinguish it from
// url.URL.Host; this is similar to the a.href.host[name] quirk in Javascript.
HostName string
Scheme string
// A port is actually an integer, so we go against normal Go convention
// and call it "PortString" to be painfully clear.
// TODO: Should we allow this to be empty if the port is unspecified?
PortString string
// TODO: Add a port int?
}

// New creates an origin based on the origin of the given URL string
func New(u *url.URL) (Origin, error) {
hostName, portString, err := net.SplitHostPort(canonicalAddr(u))
if err != nil {
return Origin{}, err
}

return Origin{
HostName: hostName,
Scheme: u.Scheme,
PortString: portString,
}, nil
}

// Parse is a convenience function that parses a URL and then calls
// New().
func Parse(urlString string) (Origin, error) {
u, err := url.Parse(urlString)
if err != nil {
return Origin{}, err
}

return New(u)
}

// // NOTE: This currently always includes the port.
// func (o Origin) String() string {
// u := url.URL{
// Scheme: o.Scheme,
// Host: net.JoinHostPort(o.HostName, o.PortString),
// }
// return u.String()
// }
36 changes: 36 additions & 0 deletions origin/origin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package origin

import (
"reflect"
"testing"
)

func TestParse(t *testing.T) {

cases := []struct {
url string
wanted Origin
}{
{"http://localhost:8080", Origin{HostName: "localhost", Scheme: "http", PortString: "8080"}},
{"http://example.com", Origin{HostName: "example.com", Scheme: "http", PortString: "80"}},
{"http://example.com/", Origin{HostName: "example.com", Scheme: "http", PortString: "80"}},
{"http://example.com:80", Origin{HostName: "example.com", Scheme: "http", PortString: "80"}},
{"http://example.com/path", Origin{HostName: "example.com", Scheme: "http", PortString: "80"}},
{"https://example.com/#yolo", Origin{HostName: "example.com", Scheme: "https", PortString: "443"}},
{"https://example.com:443", Origin{HostName: "example.com", Scheme: "https", PortString: "443"}},
{"https://a.b.example.com", Origin{HostName: "a.b.example.com", Scheme: "https", PortString: "443"}},
{"https://alice@a.b.example.com:9001/path", Origin{HostName: "a.b.example.com", Scheme: "https", PortString: "9001"}},
}

for _, tt := range cases {
o, err := Parse(tt.url)
if err != nil {
t.Fatal(err)
}

if !reflect.DeepEqual(o, tt.wanted) {
t.Errorf("Unexpected: %#v", o)
}
}

}

0 comments on commit 4bd3657

Please sign in to comment.