diff --git a/multiaddr_test.go b/multiaddr_test.go index b33e94d..8686cd9 100644 --- a/multiaddr_test.go +++ b/multiaddr_test.go @@ -627,6 +627,7 @@ func TestRoundTrip(t *testing.T) { "/ip4/127.0.0.1/udp/1234/quic-v1/webtransport/certhash/uEiDDq4_xNyDorZBH3TlGazyJdOWSwvo4PUo5YHFMrvDE8g", "/p2p/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP", "/p2p/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP/unix/a/b/c", + "/http-path/tmp%2Fbar", } { ma, err := NewMultiaddr(s) if err != nil { @@ -923,3 +924,36 @@ func TestDNS(t *testing.T) { t.Fatal("expected equality") } } + +func TestHTTPPath(t *testing.T) { + t.Run("bad addr", func(t *testing.T) { + badAddr := "/http-path/thisIsMissingAfullBytes%f" + _, err := NewMultiaddr(badAddr) + require.Error(t, err) + }) + + t.Run("round trip", func(t *testing.T) { + cases := []string{ + "/http-path/tmp%2Fbar", + "/http-path/tmp%2Fbar%2Fbaz", + "/http-path/foo", + } + for _, c := range cases { + m, err := NewMultiaddr(c) + require.NoError(t, err) + require.Equal(t, c, m.String()) + } + }) + + t.Run("value for protocol", func(t *testing.T) { + m := StringCast("/http-path/tmp%2Fbar") + v, err := m.ValueForProtocol(P_HTTP_PATH) + require.NoError(t, err) + // This gives us the url escaped version + require.Equal(t, "tmp%2Fbar", v) + + // If we want the raw unescaped version, we can use the component and read it + _, component := SplitLast(m) + require.Equal(t, "tmp/bar", string(component.RawValue())) + }) +} diff --git a/protocols.go b/protocols.go index b01e6cb..d3117b2 100644 --- a/protocols.go +++ b/protocols.go @@ -26,6 +26,7 @@ const ( P_P2P = 421 P_IPFS = P_P2P // alias for backwards compatibility P_HTTP = 480 + P_HTTP_PATH = 481 P_HTTPS = 443 // deprecated alias for /tls/http P_ONION = 444 // also for backwards compatibility P_ONION3 = 445 @@ -206,6 +207,13 @@ var ( Code: P_HTTP, VCode: CodeToVarint(P_HTTP), } + protoHTTPPath = Protocol{ + Name: "http-path", + Code: P_HTTP_PATH, + VCode: CodeToVarint(P_HTTP_PATH), + Size: LengthPrefixedVarSize, + Transcoder: TranscoderHTTPPath, + } protoHTTPS = Protocol{ Name: "https", Code: P_HTTPS, @@ -301,6 +309,7 @@ func init() { protoWEBTRANSPORT, protoCERTHASH, protoHTTP, + protoHTTPPath, protoHTTPS, protoP2P, protoUNIX, diff --git a/transcoders.go b/transcoders.go index 5387740..18d7a5a 100644 --- a/transcoders.go +++ b/transcoders.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net" + "net/url" "strconv" "strings" @@ -454,3 +455,18 @@ func validateCertHash(b []byte) error { _, err := mh.Decode(b) return err } + +var TranscoderHTTPPath = NewTranscoderFromFunctions(httpPathStB, httpPathBtS, validateHTTPPath) + +func httpPathStB(s string) ([]byte, error) { + unescaped, err := url.QueryUnescape(s) + return []byte(unescaped), err +} + +func httpPathBtS(b []byte) (string, error) { + return url.QueryEscape(string(b)), nil +} + +func validateHTTPPath(b []byte) error { + return nil // We can represent any byte slice when we escape it. +}