Skip to content

Commit

Permalink
url: fix surrogate handling in encodeAuth()
Browse files Browse the repository at this point in the history
Currently, the legacy URL stringifier miscalculates the offset of an
extra surrogate, causing the high surrogate to be included unescaped:

    > url.format({ auth: '\uD83D\uDE00', hostname: 'a' })
    '//%F0%9F%98%80�@A'

PR-URL: nodejs#11387
Backport-of: nodejs#11161
  • Loading branch information
TimothyGu committed Mar 4, 2017
1 parent 4e90a13 commit 8100f59
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 21 deletions.
50 changes: 30 additions & 20 deletions lib/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -948,44 +948,53 @@ function spliceOne(list, index) {
var hexTable = new Array(256);
for (var i = 0; i < 256; ++i)
hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();

// These characters do not need escaping:
// ! - . _ ~
// ' ( ) * :
// digits
// alpha (uppercase)
// alpha (lowercase)
const noEscapeAuth = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 - 0x0F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 - 0x1F
0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, // 0x20 - 0x2F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 0x30 - 0x3F
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 0x50 - 0x5F
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0 // 0x70 - 0x7F
];

function encodeAuth(str) {
// faster encodeURIComponent alternative for encoding auth uri components
var out = '';
var lastPos = 0;
for (var i = 0; i < str.length; ++i) {
var c = str.charCodeAt(i);

// These characters do not need escaping:
// ! - . _ ~
// ' ( ) * :
// digits
// alpha (uppercase)
// alpha (lowercase)
if (c === 0x21 || c === 0x2D || c === 0x2E || c === 0x5F || c === 0x7E ||
(c >= 0x27 && c <= 0x2A) ||
(c >= 0x30 && c <= 0x3A) ||
(c >= 0x41 && c <= 0x5A) ||
(c >= 0x61 && c <= 0x7A)) {
continue;
}

if (i - lastPos > 0)
out += str.slice(lastPos, i);

lastPos = i + 1;

// Other ASCII characters
// ASCII
if (c < 0x80) {
if (noEscapeAuth[c] === 1)
continue;
if (lastPos < i)
out += str.slice(lastPos, i);
lastPos = i + 1;
out += hexTable[c];
continue;
}

if (lastPos < i)
out += str.slice(lastPos, i);

// Multi-byte characters ...
if (c < 0x800) {
lastPos = i + 1;
out += hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)];
continue;
}
if (c < 0xD800 || c >= 0xE000) {
lastPos = i + 1;
out += hexTable[0xE0 | (c >> 12)] +
hexTable[0x80 | ((c >> 6) & 0x3F)] +
hexTable[0x80 | (c & 0x3F)];
Expand All @@ -998,6 +1007,7 @@ function encodeAuth(str) {
c2 = str.charCodeAt(i) & 0x3FF;
else
c2 = 0;
lastPos = i + 1;
c = 0x10000 + (((c & 0x3FF) << 10) | c2);
out += hexTable[0xF0 | (c >> 18)] +
hexTable[0x80 | ((c >> 12) & 0x3F)] +
Expand Down
13 changes: 12 additions & 1 deletion test/parallel/test-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -891,8 +891,19 @@ var parseTests = {
pathname: '/*',
path: '/*',
href: 'https:///*'
}
},

// surrogate in auth
'http://%F0%9F%98%80@www.example.com/': {
href: 'http://%F0%9F%98%80@www.example.com/',
slashes: true,
protocol: 'http:',
auth: '\uD83D\uDE00',
host: 'www.example.com',
hostname: 'www.example.com',
pathname: '/',
path: '/'
}
};

for (const u in parseTests) {
Expand Down

0 comments on commit 8100f59

Please sign in to comment.