feat: assume HTTP when multiaddr ends with /tcp (#7)

This makes multiaddrs ending with `/tcp/8080` to default to HTTP unless
an explicit assumeHttp: false is passed.

We also skip default ports for HTTP and HTTPS in the URL.

Motivation:
https://github.com/ipfs/js-ipfs/pull/2358#issue-307463029

License: MIT
Signed-off-by: Marcin Rataj <lidel@lidel.org>
This commit is contained in:
Marcin Rataj 2019-08-21 12:09:35 +02:00 committed by Alan Shaw
parent ad08d43abb
commit adda9366c1
3 changed files with 62 additions and 9 deletions

View File

@ -18,10 +18,19 @@ const toUri = require('multiaddr-to-uri')
console.log(toUri('/dnsaddr/protocol.ai/https')) console.log(toUri('/dnsaddr/protocol.ai/https'))
// -> https://protocol.ai // -> https://protocol.ai
console.log(toUri('/ip4/127.0.0.1/tcp/8080'))
// -> http://127.0.0.1:8080
console.log(toUri('/ip4/127.0.0.1/tcp/8080', { assumeHttp: false }))
// -> tcp://127.0.0.1:8080
``` ```
Note: Note:
* When `/tcp` is the last (terminating) protocol HTTP is assumed by default (implicit `assumeHttp: true`)
* this means produced URIs will start with `http://` instead of `tcp://`
* passing `{ assumeHttp: false }` disables this behavior
* Might be lossy - e.g. a DNSv6 multiaddr * Might be lossy - e.g. a DNSv6 multiaddr
* Can throw if the passed multiaddr: * Can throw if the passed multiaddr:
* is not a valid multiaddr * is not a valid multiaddr

View File

@ -1,5 +1,20 @@
const Multiaddr = require('multiaddr') const Multiaddr = require('multiaddr')
const reduceValue = (_, v) => v const reduceValue = (_, v) => v
const tcpUri = (str, port, parts, opts) => {
// return tcp when explicitly requested
if (opts && opts.assumeHttp === false) return `tcp://${str}:${port}`
// check if tcp is the last protocol in multiaddr
let protocol = 'tcp'
let explicitPort = `:${port}`
const last = parts[parts.length - 1]
if (last.protocol === 'tcp') {
// assume http and produce clean urls
protocol = port === 443 ? 'https' : 'http'
explicitPort = port === 443 || port === 80 ? '' : explicitPort
}
return `${protocol}://${str}${explicitPort}`
}
const Reducers = { const Reducers = {
ip4: reduceValue, ip4: reduceValue,
@ -8,10 +23,10 @@ const Reducers = {
? content ? content
: `[${content}]` : `[${content}]`
), ),
tcp: (str, content, i, parts) => ( tcp: (str, content, i, parts, opts) => (
parts.some(p => ['http', 'https', 'ws', 'wss'].includes(p.protocol)) parts.some(p => ['http', 'https', 'ws', 'wss'].includes(p.protocol))
? `${str}:${content}` ? `${str}:${content}`
: `tcp://${str}:${content}` : tcpUri(str, content, parts, opts)
), ),
udp: (str, content) => `udp://${str}:${content}`, udp: (str, content) => `udp://${str}:${content}`,
dnsaddr: reduceValue, dnsaddr: reduceValue,
@ -28,7 +43,7 @@ const Reducers = {
'p2p-webrtc-direct': str => `${str}/p2p-webrtc-direct` 'p2p-webrtc-direct': str => `${str}/p2p-webrtc-direct`
} }
module.exports = (multiaddr) => ( module.exports = (multiaddr, opts) => (
Multiaddr(multiaddr) Multiaddr(multiaddr)
.stringTuples() .stringTuples()
.map(tuple => ({ .map(tuple => ({
@ -38,6 +53,6 @@ module.exports = (multiaddr) => (
.reduce((str, part, i, parts) => { .reduce((str, part, i, parts) => {
const reduce = Reducers[part.protocol] const reduce = Reducers[part.protocol]
if (!reduce) throw new Error(`Unsupported protocol ${part.protocol}`) if (!reduce) throw new Error(`Unsupported protocol ${part.protocol}`)
return reduce(str, part.content, i, parts) return reduce(str, part.content, i, parts, opts)
}, '') }, '')
) )

39
test.js
View File

@ -7,19 +7,14 @@ test('should convert multiaddr to URI', (t) => {
['/ip4/127.0.0.1/http', 'http://127.0.0.1'], ['/ip4/127.0.0.1/http', 'http://127.0.0.1'],
['/ip6/fc00::', 'fc00::'], ['/ip6/fc00::', 'fc00::'],
['/ip6/fc00::/http', 'http://[fc00::]'], ['/ip6/fc00::/http', 'http://[fc00::]'],
['/ip4/0.0.7.6/tcp/1234', 'tcp://0.0.7.6:1234'],
['/ip4/0.0.7.6/tcp/1234/http', 'http://0.0.7.6:1234'], ['/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/https', 'https://0.0.7.6:1234'],
['/ip6/::/tcp/0', 'tcp://[::]:0'],
['/ip4/0.0.7.6/udp/1234', 'udp://0.0.7.6:1234'], ['/ip4/0.0.7.6/udp/1234', 'udp://0.0.7.6:1234'],
['/ip6/::/udp/0', 'udp://[::]:0'], ['/ip6/::/udp/0', 'udp://[::]:0'],
['/dnsaddr/ipfs.io', 'ipfs.io'], ['/dnsaddr/ipfs.io', 'ipfs.io'],
['/dns4/ipfs.io', 'ipfs.io'], ['/dns4/ipfs.io', 'ipfs.io'],
['/dns4/libp2p.io', 'libp2p.io'], ['/dns4/libp2p.io', 'libp2p.io'],
['/dns6/protocol.ai', 'protocol.ai'], ['/dns6/protocol.ai', 'protocol.ai'],
['/dns4/protocol.ai/tcp/80', 'tcp://protocol.ai:80'],
['/dns6/protocol.ai/tcp/80', 'tcp://protocol.ai:80'],
['/dnsaddr/protocol.ai/tcp/80', 'tcp://protocol.ai:80'],
['/dnsaddr/protocol.ai/tcp/80/http', 'http://protocol.ai:80'], ['/dnsaddr/protocol.ai/tcp/80/http', 'http://protocol.ai:80'],
['/dnsaddr/protocol.ai/tcp/80/https', 'https://protocol.ai:80'], ['/dnsaddr/protocol.ai/tcp/80/https', 'https://protocol.ai:80'],
['/dnsaddr/ipfs.io/ws', 'ws://ipfs.io'], ['/dnsaddr/ipfs.io/ws', 'ws://ipfs.io'],
@ -83,6 +78,40 @@ test('should convert multiaddr to URI', (t) => {
data.forEach(d => t.is(toUri(d[0]), d[1])) data.forEach(d => t.is(toUri(d[0]), d[1]))
}) })
test('should convert multiaddr to http(s):// URI when implicit { assumeHttp: true }', (t) => {
const data = [
['/ip4/0.0.7.6/tcp/1234', 'http://0.0.7.6:1234'],
['/ip6/::/tcp/0', 'http://[::]:0'],
['/dns4/protocol.ai/tcp/80', 'http://protocol.ai'],
['/dns6/protocol.ai/tcp/80', 'http://protocol.ai'],
['/dns4/protocol.ai/tcp/8080', 'http://protocol.ai:8080'],
['/dns6/protocol.ai/tcp/8080', 'http://protocol.ai:8080'],
['/dns4/protocol.ai/tcp/443', 'https://protocol.ai'],
['/dns6/protocol.ai/tcp/443', 'https://protocol.ai'],
['/dnsaddr/protocol.ai/tcp/80', 'http://protocol.ai'],
['/dnsaddr/protocol.ai/tcp/443', 'https://protocol.ai'],
['/dnsaddr/protocol.ai/tcp/8080', 'http://protocol.ai:8080']
]
data.forEach(d => t.is(toUri(d[0]), d[1]))
})
test('should convert multiaddr to tcp:// URI when explicit { assumeHttp: false }', (t) => {
const data = [
['/ip4/0.0.7.6/tcp/1234', 'tcp://0.0.7.6:1234'],
['/ip6/::/tcp/0', 'tcp://[::]:0'],
['/dns4/protocol.ai/tcp/80', 'tcp://protocol.ai:80'],
['/dns6/protocol.ai/tcp/80', 'tcp://protocol.ai:80'],
['/dns4/protocol.ai/tcp/8080', 'tcp://protocol.ai:8080'],
['/dns6/protocol.ai/tcp/8080', 'tcp://protocol.ai:8080'],
['/dns4/protocol.ai/tcp/443', 'tcp://protocol.ai:443'],
['/dns6/protocol.ai/tcp/443', 'tcp://protocol.ai:443'],
['/dnsaddr/protocol.ai/tcp/80', 'tcp://protocol.ai:80'],
['/dnsaddr/protocol.ai/tcp/443', 'tcp://protocol.ai:443'],
['/dnsaddr/protocol.ai/tcp/8080', 'tcp://protocol.ai:8080']
]
data.forEach(d => t.is(toUri(d[0], { assumeHttp: false }), d[1]))
})
test('should throw for unsupported protocol', (t) => { test('should throw for unsupported protocol', (t) => {
t.throws(() => toUri('/quic')) t.throws(() => toUri('/quic'))
}) })