url.cjs 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. 'use strict';
  2. const charCodeDefinitions = require('../tokenizer/char-code-definitions.cjs');
  3. const utils = require('../tokenizer/utils.cjs');
  4. const SPACE = 0x0020; // U+0020 SPACE
  5. const REVERSE_SOLIDUS = 0x005c; // U+005C REVERSE SOLIDUS (\)
  6. const QUOTATION_MARK = 0x0022; // "
  7. const APOSTROPHE = 0x0027; // '
  8. const LEFTPARENTHESIS = 0x0028; // U+0028 LEFT PARENTHESIS (()
  9. const RIGHTPARENTHESIS = 0x0029; // U+0029 RIGHT PARENTHESIS ())
  10. function decode(str) {
  11. const len = str.length;
  12. let start = 4; // length of "url("
  13. let end = str.charCodeAt(len - 1) === RIGHTPARENTHESIS ? len - 2 : len - 1;
  14. let decoded = '';
  15. while (start < end && charCodeDefinitions.isWhiteSpace(str.charCodeAt(start))) {
  16. start++;
  17. }
  18. while (start < end && charCodeDefinitions.isWhiteSpace(str.charCodeAt(end))) {
  19. end--;
  20. }
  21. for (let i = start; i <= end; i++) {
  22. let code = str.charCodeAt(i);
  23. if (code === REVERSE_SOLIDUS) {
  24. // special case at the ending
  25. if (i === end) {
  26. // if the next input code point is EOF, do nothing
  27. // otherwise include last left parenthesis as escaped
  28. if (i !== len - 1) {
  29. decoded = str.substr(i + 1);
  30. }
  31. break;
  32. }
  33. code = str.charCodeAt(++i);
  34. // consume escaped
  35. if (charCodeDefinitions.isValidEscape(REVERSE_SOLIDUS, code)) {
  36. const escapeStart = i - 1;
  37. const escapeEnd = utils.consumeEscaped(str, escapeStart);
  38. i = escapeEnd - 1;
  39. decoded += utils.decodeEscaped(str.substring(escapeStart + 1, escapeEnd));
  40. } else {
  41. // \r\n
  42. if (code === 0x000d && str.charCodeAt(i + 1) === 0x000a) {
  43. i++;
  44. }
  45. }
  46. } else {
  47. decoded += str[i];
  48. }
  49. }
  50. return decoded;
  51. }
  52. function encode(str) {
  53. let encoded = '';
  54. let wsBeforeHexIsNeeded = false;
  55. for (let i = 0; i < str.length; i++) {
  56. const code = str.charCodeAt(i);
  57. // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER (U+FFFD).
  58. if (code === 0x0000) {
  59. encoded += '\uFFFD';
  60. continue;
  61. }
  62. // If the character is in the range [\1-\1f] (U+0001 to U+001F) or is U+007F,
  63. // the character escaped as code point.
  64. // Note: Do not compare with 0x0001 since 0x0000 is precessed before
  65. if (code <= 0x001f || code === 0x007F) {
  66. encoded += '\\' + code.toString(16);
  67. wsBeforeHexIsNeeded = true;
  68. continue;
  69. }
  70. if (code === SPACE ||
  71. code === REVERSE_SOLIDUS ||
  72. code === QUOTATION_MARK ||
  73. code === APOSTROPHE ||
  74. code === LEFTPARENTHESIS ||
  75. code === RIGHTPARENTHESIS) {
  76. encoded += '\\' + str.charAt(i);
  77. wsBeforeHexIsNeeded = false;
  78. } else {
  79. if (wsBeforeHexIsNeeded && charCodeDefinitions.isHexDigit(code)) {
  80. encoded += ' ';
  81. }
  82. encoded += str.charAt(i);
  83. wsBeforeHexIsNeeded = false;
  84. }
  85. }
  86. return 'url(' + encoded + ')';
  87. }
  88. exports.decode = decode;
  89. exports.encode = encode;