index.js 2.1 KB

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