Skip to content

Commit

Permalink
fix: handle ports in multiaddrs with SNI tuples (#154)
Browse files Browse the repository at this point in the history
When non-default http/https ports are encountered, ensure we set
them in the final URI correctly.

Also remove default 80/443 ports from `ws://` and `wss://` URIs
same as `http://` and `https://`.
  • Loading branch information
achingbrain authored Oct 28, 2024
1 parent c3036d6 commit 9fd5bdb
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 34 deletions.
75 changes: 46 additions & 29 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,35 @@ const ASSUME_HTTP_CODES = [

interface Interpreter { (value: string, ma: StringTuple[]): string }

function extractSNI (ma: StringTuple[]): string | null {
let sniProtoCode: number
function extractSNI (ma: StringTuple[]): string | undefined {
return extractTuple('sni', ma)?.[1]
}

function extractPort (ma: StringTuple[]): string {
const port = extractTuple('tcp', ma)?.[1]

if (port == null) {
return ''
}

return `:${port}`
}

function extractTuple (name: string, ma: StringTuple[]): StringTuple | undefined {
let code: number

try {
sniProtoCode = protocols('sni').code
code = protocols(name).code
} catch (e) {
// No SNI protocol in multiaddr
return null
// No support for protocol in multiaddr
return
}

for (const [proto, value] of ma) {
if (proto === sniProtoCode && value !== undefined) {
return value
if (proto === code && value != null) {
return [proto, value]
}
}
return null
}

function hasTLS (ma: StringTuple[]): boolean {
Expand All @@ -68,7 +83,7 @@ function hasTLS (ma: StringTuple[]): boolean {

function interpretNext (headProtoCode: number, headProtoVal: string, restMa: StringTuple[]): string {
const interpreter = interpreters[protocols(headProtoCode).name]
if (interpreter === undefined) {
if (interpreter == null) {
throw new Error(`Can't interpret protocol ${protocols(headProtoCode).name}`)
}
const restVal = interpreter(headProtoVal, restMa)
Expand All @@ -88,14 +103,14 @@ const interpreters: Record<string, Interpreter> = {
},
tcp: (value: string, restMa: StringTuple[]) => {
const tailProto = restMa.pop()
if (tailProto === undefined) {
if (tailProto == null) {
throw new Error('Unexpected end of multiaddr')
}
return `tcp://${interpretNext(tailProto[0], tailProto[1] ?? '', restMa)}:${value}`
},
udp: (value: string, restMa: StringTuple[]) => {
const tailProto = restMa.pop()
if (tailProto === undefined) {
if (tailProto == null) {
throw new Error('Unexpected end of multiaddr')
}
return `udp://${interpretNext(tailProto[0], tailProto[1] ?? '', restMa)}:${value}`
Expand All @@ -106,27 +121,28 @@ const interpreters: Record<string, Interpreter> = {
dns: (value: string, restMa: StringTuple[]) => value,
ipfs: (value: string, restMa: StringTuple[]) => {
const tailProto = restMa.pop()
if (tailProto === undefined) {
if (tailProto == null) {
throw new Error('Unexpected end of multiaddr')
}
return `${interpretNext(tailProto[0], tailProto[1] ?? '', restMa)}/ipfs/${value}`
},
p2p: (value: string, restMa: StringTuple[]) => {
const tailProto = restMa.pop()
if (tailProto === undefined) {
if (tailProto == null) {
throw new Error('Unexpected end of multiaddr')
}
return `${interpretNext(tailProto[0], tailProto[1] ?? '', restMa)}/p2p/${value}`
},
http: (value: string, restMa: StringTuple[]) => {
const maHasTLS = hasTLS(restMa)
const sni = extractSNI(restMa)
if (maHasTLS && sni !== null) {
return `https://${sni}`
const port = extractPort(restMa)
if (maHasTLS && sni != null) {
return `https://${sni}${port}`
}
const protocol = maHasTLS ? 'https://' : 'http://'
const tailProto = restMa.pop()
if (tailProto === undefined) {
if (tailProto == null) {
throw new Error('Unexpected end of multiaddr')
}
let baseVal = interpretNext(tailProto[0], tailProto[1] ?? '', restMa)
Expand All @@ -136,7 +152,7 @@ const interpreters: Record<string, Interpreter> = {
},
'http-path': (value: string, restMa: StringTuple[]) => {
const tailProto = restMa.pop()
if (tailProto === undefined) {
if (tailProto == null) {
throw new Error('Unexpected end of multiaddr')
}
const baseVal = interpretNext(tailProto[0], tailProto[1] ?? '', restMa)
Expand All @@ -147,7 +163,7 @@ const interpreters: Record<string, Interpreter> = {
// Noop, the parent context knows that it's tls. We don't need to do
// anything here
const tailProto = restMa.pop()
if (tailProto === undefined) {
if (tailProto == null) {
throw new Error('Unexpected end of multiaddr')
}
return interpretNext(tailProto[0], tailProto[1] ?? '', restMa)
Expand All @@ -156,14 +172,14 @@ const interpreters: Record<string, Interpreter> = {
// Noop, the parent context uses the sni information, we don't need to do
// anything here
const tailProto = restMa.pop()
if (tailProto === undefined) {
if (tailProto == null) {
throw new Error('Unexpected end of multiaddr')
}
return interpretNext(tailProto[0], tailProto[1] ?? '', restMa)
},
https: (value: string, restMa: StringTuple[]) => {
const tailProto = restMa.pop()
if (tailProto === undefined) {
if (tailProto == null) {
throw new Error('Unexpected end of multiaddr')
}
let baseVal = interpretNext(tailProto[0], tailProto[1] ?? '', restMa)
Expand All @@ -174,12 +190,13 @@ const interpreters: Record<string, Interpreter> = {
ws: (value: string, restMa: StringTuple[]) => {
const maHasTLS = hasTLS(restMa)
const sni = extractSNI(restMa)
if (maHasTLS && sni !== null) {
return `wss://${sni}`
const port = extractPort(restMa)
if (maHasTLS && sni != null) {
return `wss://${sni}${port}`
}
const protocol = maHasTLS ? 'wss://' : 'ws://'
const tailProto = restMa.pop()
if (tailProto === undefined) {
if (tailProto == null) {
throw new Error('Unexpected end of multiaddr')
}
let baseVal = interpretNext(tailProto[0], tailProto[1] ?? '', restMa)
Expand All @@ -189,7 +206,7 @@ const interpreters: Record<string, Interpreter> = {
},
wss: (value: string, restMa: StringTuple[]) => {
const tailProto = restMa.pop()
if (tailProto === undefined) {
if (tailProto == null) {
throw new Error('Unexpected end of multiaddr')
}
let baseVal = interpretNext(tailProto[0], tailProto[1] ?? '', restMa)
Expand All @@ -199,21 +216,21 @@ const interpreters: Record<string, Interpreter> = {
},
'p2p-websocket-star': (value: string, restMa: StringTuple[]) => {
const tailProto = restMa.pop()
if (tailProto === undefined) {
if (tailProto == null) {
throw new Error('Unexpected end of multiaddr')
}
return `${interpretNext(tailProto[0], tailProto[1] ?? '', restMa)}/p2p-websocket-star`
},
'p2p-webrtc-star': (value: string, restMa: StringTuple[]) => {
const tailProto = restMa.pop()
if (tailProto === undefined) {
if (tailProto == null) {
throw new Error('Unexpected end of multiaddr')
}
return `${interpretNext(tailProto[0], tailProto[1] ?? '', restMa)}/p2p-webrtc-star`
},
'p2p-webrtc-direct': (value: string, restMa: StringTuple[]) => {
const tailProto = restMa.pop()
if (tailProto === undefined) {
if (tailProto == null) {
throw new Error('Unexpected end of multiaddr')
}
return `${interpretNext(tailProto[0], tailProto[1] ?? '', restMa)}/p2p-webrtc-direct`
Expand All @@ -224,7 +241,7 @@ export function multiaddrToUri (input: Multiaddr | string | Uint8Array, opts?: M
const ma = multiaddr(input)
const parts = ma.stringTuples()
const head = parts.pop()
if (head === undefined) {
if (head == null) {
throw new Error('Unexpected end of multiaddr')
}

Expand All @@ -248,7 +265,7 @@ export function multiaddrToUri (input: Multiaddr | string | Uint8Array, opts?: M
}
}

if (uri.startsWith('http://') || uri.startsWith('https://')) {
if (uri.startsWith('http://') || uri.startsWith('https://') || uri.startsWith('ws://') || uri.startsWith('wss://')) {
// this will strip default ports while keeping paths intact
uri = new URL(uri).toString()

Expand Down
13 changes: 8 additions & 5 deletions test/test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ describe('multiaddr-to-uri', () => {
['/ip4/0.0.7.6/tcp/1234/http', 'http://0.0.7.6:1234'],
['/ip4/0.0.7.6/tcp/1234/https', 'https://0.0.7.6:1234'],
['/ip4/0.0.7.6/tcp/1234/tls/http', 'https://0.0.7.6:1234'],
['/ip4/1.2.3.4/tcp/1234/tls/sni/ipfs.io/http', 'https://ipfs.io'],
['/ip4/1.2.3.4/tcp/1234/tls/sni/ipfs.io/http/http-path/foo%2fbar', 'https://ipfs.io/foo/bar'],
['/ip4/1.2.3.4/tcp/443/tls/sni/ipfs.io/http', 'https://ipfs.io'],
['/ip4/1.2.3.4/tcp/1234/tls/sni/ipfs.io/http', 'https://ipfs.io:1234'],
['/ip4/1.2.3.4/tcp/443/tls/sni/ipfs.io/http/http-path/foo%2fbar', 'https://ipfs.io/foo/bar'],
['/ip4/1.2.3.4/tcp/1234/tls/sni/ipfs.io/http/http-path/foo%2fbar', 'https://ipfs.io:1234/foo/bar'],
['/ip4/0.0.7.6/udp/1234', 'udp://0.0.7.6:1234'],
['/ip6/::/udp/0', 'udp://[::]:0'],
['/dns/a.com/tcp/1234', 'http://a.com:1234'],
Expand All @@ -35,7 +37,8 @@ describe('multiaddr-to-uri', () => {
['/ip6/::/tcp/0/ws', 'ws://[::]:0'],
['/dnsaddr/ipfs.io/wss', 'wss://ipfs.io'],
['/dnsaddr/ipfs.io/tls/ws', 'wss://ipfs.io'],
['/ip4/1.2.3.4/tcp/1234/tls/sni/ipfs.io/ws', 'wss://ipfs.io'],
['/ip4/1.2.3.4/tcp/443/tls/sni/ipfs.io/ws', 'wss://ipfs.io'],
['/ip4/1.2.3.4/tcp/1234/tls/sni/ipfs.io/ws', 'wss://ipfs.io:1234'],
['/ip4/1.2.3.4/tcp/3456/wss', 'wss://1.2.3.4:3456'],
['/ip6/::/tcp/0/wss', 'wss://[::]:0'],
[
Expand All @@ -56,7 +59,7 @@ describe('multiaddr-to-uri', () => {
],
[
'/dns4/wrtc-star.discovery.libp2p.io/tcp/443/wss/p2p-webrtc-star/ipfs/QmTysQQiTGMdfRsDQp516oZ9bR3FiSCDnicUnqny2q1d79',
'wss://wrtc-star.discovery.libp2p.io:443/p2p-webrtc-star/p2p/QmTysQQiTGMdfRsDQp516oZ9bR3FiSCDnicUnqny2q1d79'
'wss://wrtc-star.discovery.libp2p.io/p2p-webrtc-star/p2p/QmTysQQiTGMdfRsDQp516oZ9bR3FiSCDnicUnqny2q1d79'
],
['/ip4/1.2.3.4/tcp/3456/http/p2p-webrtc-direct', 'http://1.2.3.4:3456/p2p-webrtc-direct'],
['/ip6/::/tcp/0/http/p2p-webrtc-direct', 'http://[::]:0/p2p-webrtc-direct'],
Expand All @@ -72,7 +75,7 @@ describe('multiaddr-to-uri', () => {
],
[
'/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star/ipfs/QmP3vadpN9dqZ7j6KtmwP5Y4prg7XqdS7ixgZMWtXxBAbp',
'wss://ws-star.discovery.libp2p.io:443/p2p-websocket-star/p2p/QmP3vadpN9dqZ7j6KtmwP5Y4prg7XqdS7ixgZMWtXxBAbp'
'wss://ws-star.discovery.libp2p.io/p2p-websocket-star/p2p/QmP3vadpN9dqZ7j6KtmwP5Y4prg7XqdS7ixgZMWtXxBAbp'
],
[
'/ip4/127.0.0.1/tcp/20008/ws/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj',
Expand Down

0 comments on commit 9fd5bdb

Please sign in to comment.