164 lines
4.6 KiB
Plaintext
164 lines
4.6 KiB
Plaintext
'use strict'
|
|
|
|
const test = require('tape')
|
|
const fastURI = require('..')
|
|
|
|
test('userinfo serialization cannot terminate the authority', (t) => {
|
|
const uri = fastURI.serialize({
|
|
scheme: 'http',
|
|
userinfo: 'attacker.example/',
|
|
host: 'trusted.example',
|
|
path: '/'
|
|
})
|
|
|
|
t.equal(uri, 'http://attacker.example%2F@trusted.example/', 'slash is encoded as userinfo data')
|
|
|
|
const reparsed = fastURI.parse(uri)
|
|
t.equal(reparsed.host, 'trusted.example', 'reparsing retains the supplied host')
|
|
t.equal(reparsed.userinfo, 'attacker.example%2F', 'encoded slash remains in userinfo')
|
|
t.equal(
|
|
fastURI.serialize({ userinfo: 'user@/?:#[]\\', host: 'example.test' }),
|
|
'//user%40%2F%3F:%23%5B%5D%5C@example.test',
|
|
'all userinfo component delimiters are encoded as data'
|
|
)
|
|
t.end()
|
|
})
|
|
|
|
test('port serialization rejects non-digit values', (t) => {
|
|
const malformedPorts = [
|
|
'@127.0.0.1:8124',
|
|
'8080@evil.example',
|
|
'8080/path',
|
|
'8080?query',
|
|
'8080#fragment',
|
|
'8080:9000',
|
|
'-1',
|
|
'1.5',
|
|
1.5,
|
|
NaN,
|
|
Infinity,
|
|
'\u0661'
|
|
]
|
|
|
|
for (const port of malformedPorts) {
|
|
t.throws(
|
|
() => fastURI.serialize({ scheme: 'http', host: 'trusted.example', port, path: '/app' }),
|
|
/URI port is malformed\./,
|
|
String(port)
|
|
)
|
|
}
|
|
|
|
t.throws(
|
|
() => fastURI.normalize({ scheme: 'http', host: 'trusted.example', port: '@evil.example' }),
|
|
/URI port is malformed\./,
|
|
'object normalization rejects a malformed port'
|
|
)
|
|
t.equal(
|
|
fastURI.equal(
|
|
{ scheme: 'http', host: 'trusted.example', port: '@evil.example' },
|
|
{ scheme: 'http', host: 'trusted.example', port: '@evil.example' }
|
|
),
|
|
false,
|
|
'object equality fails closed for a malformed port'
|
|
)
|
|
t.end()
|
|
})
|
|
|
|
test('port serialization preserves RFC 3986 digit values', (t) => {
|
|
const validPorts = [
|
|
[8080, '8080'],
|
|
['8080', '8080'],
|
|
['00080', '00080'],
|
|
['', '']
|
|
]
|
|
|
|
for (const [port, expected] of validPorts) {
|
|
t.equal(
|
|
fastURI.serialize({ scheme: 'uri', host: 'example.test', port }),
|
|
`uri://example.test:${expected}`,
|
|
JSON.stringify(port)
|
|
)
|
|
}
|
|
t.end()
|
|
})
|
|
|
|
test('query and fragment serialization cannot inject component delimiters', (t) => {
|
|
const uri = fastURI.serialize({
|
|
scheme: 'x',
|
|
path: '/resource',
|
|
query: 'key=value#still-query',
|
|
fragment: 'first#still-fragment'
|
|
})
|
|
|
|
t.equal(
|
|
uri,
|
|
'x:/resource?key=value%23still-query#first%23still-fragment',
|
|
'raw hashes are encoded within their supplied components'
|
|
)
|
|
|
|
const reparsed = fastURI.parse(uri)
|
|
t.equal(reparsed.query, 'key=value%23still-query', 'hash remains query data')
|
|
t.equal(reparsed.fragment, 'first%23still-fragment', 'hash remains fragment data')
|
|
t.end()
|
|
})
|
|
|
|
test('component serializers preserve each RFC 3986 literal character set', (t) => {
|
|
const unreservedAndSubDelims = "AZaz09-._~!$&'()*+,;="
|
|
const userinfo = unreservedAndSubDelims + ':'
|
|
const queryOrFragment = unreservedAndSubDelims + ':@/?'
|
|
|
|
t.equal(
|
|
fastURI.serialize({ userinfo, host: 'example.test' }),
|
|
'//' + userinfo + '@example.test',
|
|
'userinfo literals are preserved'
|
|
)
|
|
t.equal(
|
|
fastURI.serialize({ query: queryOrFragment }),
|
|
'?' + queryOrFragment,
|
|
'query literals are preserved'
|
|
)
|
|
t.equal(
|
|
fastURI.serialize({ fragment: queryOrFragment }),
|
|
'#' + queryOrFragment,
|
|
'fragment literals are preserved'
|
|
)
|
|
t.end()
|
|
})
|
|
|
|
test('component serializers preserve escapes and encode Unicode as UTF-8', (t) => {
|
|
t.equal(
|
|
fastURI.serialize({ userinfo: 'café/%2f%GG', host: 'example.test' }),
|
|
'//caf%C3%A9%2F%2F%25GG@example.test',
|
|
'userinfo preserves valid escapes, uppercases hex, and escapes malformed percent data'
|
|
)
|
|
t.equal(
|
|
fastURI.serialize({ query: '日本/%2f#%GG' }),
|
|
'?%E6%97%A5%E6%9C%AC/%2F%23%25GG',
|
|
'query uses UTF-8 and does not double encode valid escapes'
|
|
)
|
|
t.equal(
|
|
fastURI.serialize({ fragment: '😀/%3f#%GG' }),
|
|
'#%F0%9F%98%80/%3F%23%25GG',
|
|
'fragment uses UTF-8 and does not double encode valid escapes'
|
|
)
|
|
t.equal(
|
|
fastURI.serialize({ query: 'Kſ' }),
|
|
'?%E2%84%AA%C5%BF',
|
|
'Unicode characters that case-fold to ASCII are still UTF-8 encoded'
|
|
)
|
|
t.end()
|
|
})
|
|
|
|
test('parsed URI serialization remains stable', (t) => {
|
|
const input = 'x://user:pass@example.test/a%2Fb?x=a/b?c&y=%23#frag/a?b%23'
|
|
const serialized = fastURI.serialize(fastURI.parse(input))
|
|
|
|
t.equal(serialized, input, 'an already normalized parsed URI round-trips unchanged')
|
|
t.equal(
|
|
fastURI.serialize(fastURI.parse(serialized)),
|
|
serialized,
|
|
'repeated parse and serialize cycles are stable'
|
|
)
|
|
t.end()
|
|
})
|