url.js 3.2 KB

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