🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

fast-uri

Package Overview
Dependencies
Maintainers
11
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fast-uri - npm Package Compare versions

Comparing version
3.1.4
to
3.1.5
+37
-1
index.js

@@ -29,3 +29,8 @@ 'use strict'

const schemelessOptions = options ? Object.assign({ scheme: 'null' }, options) : { scheme: 'null' }
const resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true)
const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions)
const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions)
if (baseMalformed || relativeMalformed) {
throw new Error(baseParsed.error || relativeParsed.error || 'URI is malformed.')
}
const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true)
schemelessOptions.skipEscape = true

@@ -210,2 +215,11 @@ return serialize(resolved, schemelessOptions)

// Captures the leading authority-introducer region after an optional scheme: a
// run of forward slashes, backslashes, and the characters the WHATWG URL parser
// removes before parsing (TAB U+0009, LF U+000A, CR U+000D). A valid introducer
// is exactly "//". Node treats "\" as "/" on special schemes and strips those
// characters first, so forms like "\\", "/\", "\/", "/<TAB>/", or a leading
// "<TAB>//" reach an authority in Node while fast-uri's URI_PARSE folds them into
// the path group (host confusion / SSRF / redirect bypass).
const AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/
/**

@@ -270,2 +284,24 @@ * @param {import('./types/index').URIComponent} parsed

// Reject a malformed or whitespace-smuggled authority introducer. fast-uri
// only recognizes a literal "//"; anything else in the leading separator run
// (a backslash, or a "//" that appears only after removing the TAB/LF/CR that
// Node strips) means the authority fast-uri parses differs from the one Node's
// URL resolves. Reject rather than rewrite, mirroring the literal-backslash
// guard above. Percent-encoded forms (%5C, %09) are untouched, valid data.
const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION)
if (introducerMatch !== null) {
const region = introducerMatch[1]
const normalizedRegion = region.replace(/[\t\n\r]/g, '')
// Two or more leading separators introduce an authority.
if (normalizedRegion.length >= 2) {
if (normalizedRegion.slice(0, 2) !== '//') {
parsed.error = parsed.error || 'URI authority must not contain a literal backslash.'
malformedAuthorityOrPort = true
} else if (region.length !== normalizedRegion.length) {
parsed.error = parsed.error || 'URI authority introducer must not contain whitespace.'
malformedAuthorityOrPort = true
}
}
}
const matches = uri.match(URI_PARSE)

@@ -272,0 +308,0 @@

+1
-1
{
"name": "fast-uri",
"description": "Dependency-free RFC 3986 URI toolbox",
"version": "3.1.4",
"version": "3.1.5",
"main": "index.js",

@@ -6,0 +6,0 @@ "type": "commonjs",

@@ -224,1 +224,137 @@ 'use strict'

})
test('parse rejects a malformed authority introducer (\\\\, /\\, \\/) in place of //', (t) => {
// Regression: "\\", "/\\", "\\/" after the scheme colon are not valid authority
// introducers. Node's URL treats "\\" as interchangeable with "/" on special
// schemes, so "http:\\\\evil.com/path" would be parsed as host "evil.com" by
// Node, but fast-uri must reject it as malformed to prevent SSRF/redirect bypass.
const cases = [
'http:\\\\evil.com/path',
'http:/\\evil.com/path',
'http:\\/evil.com/path',
'ws:\\\\evil.com/chat',
'wss:\\\\evil.com/chat',
'ftp:\\\\evil.com/',
'\\\\evil.com/path'
]
t.plan(cases.length)
cases.forEach((input) => {
t.equal(
fastURI.parse(input).error,
'URI authority must not contain a literal backslash.',
input
)
})
})
test('normalize does not canonicalize a malformed-authority-introducer URI', (t) => {
const cases = [
'http:\\\\evil.com/path',
'http:/\\evil.com/path'
]
t.plan(cases.length)
cases.forEach((input) => {
t.equal(fastURI.normalize(input), input, input)
})
})
test('equal returns false for malformed-authority-introducer URIs', (t) => {
const pairs = [
['http:\\\\evil.com/path', 'http://evil.com/path'],
['http:/\\evil.com/path', 'http://evil.com/path']
]
t.plan(pairs.length)
pairs.forEach(([left, right]) => {
t.equal(fastURI.equal(left, right), false, `${left} != ${right}`)
})
})
test('resolve throws on malformed authority introducer', (t) => {
// resolve() returns a plain string with no error field, so the only safe
// behavior is to throw when either component has a malformed authority.
const pairs = [
['https://allowed.com/', '\\\\evil.com/path'],
['\\\\evil.com/path', 'https://allowed.com/'],
['https://allowed.com/', 'http:/\\evil.com/path'],
['https://allowed.com/', 'http:\\/evil.com/path']
]
t.plan(pairs.length)
pairs.forEach(([base, rel]) => {
t.throws(
() => fastURI.resolve(base, rel),
/URI authority must not contain a literal backslash/,
`${base} + ${rel}`
)
})
})
test('parse rejects a whitespace-split authority introducer (TAB, LF, CR)', (t) => {
// The WHATWG URL parser removes TAB (U+0009), LF (U+000A) and CR (U+000D) from
// the input before parsing, so a stripped character wedged into the introducer
// ("/<TAB>\\", "/<TAB>/", or a leading "<TAB>//") reaches an authority in Node
// while fast-uri would otherwise fold it into the path. These must be rejected
// like the adjacent "\\", "/\\", "\\/" forms.
const cases = [
{ input: '/\t\\evil.com/path', expectedError: 'URI authority must not contain a literal backslash.' },
{ input: '/\t/evil.com/path', expectedError: 'URI authority introducer must not contain whitespace.' },
{ input: '/\n\\evil.com/path', expectedError: 'URI authority must not contain a literal backslash.' },
{ input: '/\r\\evil.com/path', expectedError: 'URI authority must not contain a literal backslash.' },
{ input: '\t//evil.com/path', expectedError: 'URI authority introducer must not contain whitespace.' },
{ input: '\t/\\evil.com/path', expectedError: 'URI authority must not contain a literal backslash.' },
{ input: 'https:/\t/evil.com/path', expectedError: 'URI authority introducer must not contain whitespace.' }
]
t.plan(cases.length)
cases.forEach(({ input, expectedError }) => {
t.equal(fastURI.parse(input).error, expectedError, JSON.stringify(input))
})
})
test('resolve throws on a whitespace-split authority introducer', (t) => {
const pairs = [
['https://allowed.com/', '/\t\\evil.com/path'],
['https://allowed.com/', '/\t/evil.com/path'],
['https://allowed.com/', '/\n\\evil.com/path'],
['/\t/evil.com/path', 'https://allowed.com/']
]
t.plan(pairs.length)
pairs.forEach(([base, rel]) => {
t.throws(
() => fastURI.resolve(base, rel),
/URI authority (must not contain a literal backslash|introducer must not contain whitespace)/,
`${JSON.stringify(base)} + ${JSON.stringify(rel)}`
)
})
})
test('parse does not reject valid authority introducer patterns', (t) => {
// No false positives: "//" introducer and scheme-less "//" must be valid.
const cases = [
'http://good.com/',
'https://good.com/',
'ws://good.com/chat',
'wss://good.com/chat',
'ftp://good.com/',
'//good.com/path',
'/absolute/path',
'relative/path'
]
t.plan(cases.length)
cases.forEach((input) => {
const parsed = fastURI.parse(input)
t.notOk(parsed.error, input)
})
})