• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2var token = '%[a-f0-9]{2}';
3var singleMatcher = new RegExp(token, 'gi');
4var multiMatcher = new RegExp('(' + token + ')+', 'gi');
5
6function decodeComponents(components, split) {
7	try {
8		// Try to decode the entire string first
9		return decodeURIComponent(components.join(''));
10	} catch (err) {
11		// Do nothing
12	}
13
14	if (components.length === 1) {
15		return components;
16	}
17
18	split = split || 1;
19
20	// Split the array in 2 parts
21	var left = components.slice(0, split);
22	var right = components.slice(split);
23
24	return Array.prototype.concat.call([], decodeComponents(left), decodeComponents(right));
25}
26
27function decode(input) {
28	try {
29		return decodeURIComponent(input);
30	} catch (err) {
31		var tokens = input.match(singleMatcher);
32
33		for (var i = 1; i < tokens.length; i++) {
34			input = decodeComponents(tokens, i).join('');
35
36			tokens = input.match(singleMatcher);
37		}
38
39		return input;
40	}
41}
42
43function customDecodeURIComponent(input) {
44	// Keep track of all the replacements and prefill the map with the `BOM`
45	var replaceMap = {
46		'%FE%FF': '\uFFFD\uFFFD',
47		'%FF%FE': '\uFFFD\uFFFD'
48	};
49
50	var match = multiMatcher.exec(input);
51	while (match) {
52		try {
53			// Decode as big chunks as possible
54			replaceMap[match[0]] = decodeURIComponent(match[0]);
55		} catch (err) {
56			var result = decode(match[0]);
57
58			if (result !== match[0]) {
59				replaceMap[match[0]] = result;
60			}
61		}
62
63		match = multiMatcher.exec(input);
64	}
65
66	// Add `%C2` at the end of the map to make sure it does not replace the combinator before everything else
67	replaceMap['%C2'] = '\uFFFD';
68
69	var entries = Object.keys(replaceMap);
70
71	for (var i = 0; i < entries.length; i++) {
72		// Replace all decoded components
73		var key = entries[i];
74		input = input.replace(new RegExp(key, 'g'), replaceMap[key]);
75	}
76
77	return input;
78}
79
80module.exports = function (encodedURI) {
81	if (typeof encodedURI !== 'string') {
82		throw new TypeError('Expected `encodedURI` to be of type `string`, got `' + typeof encodedURI + '`');
83	}
84
85	try {
86		encodedURI = encodedURI.replace(/\+/g, ' ');
87
88		// Try the built in decoder first
89		return decodeURIComponent(encodedURI);
90	} catch (err) {
91		// Fallback to a more advanced decoder
92		return customDecodeURIComponent(encodedURI);
93	}
94};
95