index.js 2.2 KB

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