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