urlencoded.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. "use strict";
  2. const { isASCIIHex } = require("./infra");
  3. function strictlySplitByteSequence(buf, cp) {
  4. const list = [];
  5. let last = 0;
  6. let i = buf.indexOf(cp);
  7. while (i >= 0) {
  8. list.push(buf.slice(last, i));
  9. last = i + 1;
  10. i = buf.indexOf(cp, last);
  11. }
  12. if (last !== buf.length) {
  13. list.push(buf.slice(last));
  14. }
  15. return list;
  16. }
  17. function replaceByteInByteSequence(buf, from, to) {
  18. let i = buf.indexOf(from);
  19. while (i >= 0) {
  20. buf[i] = to;
  21. i = buf.indexOf(from, i + 1);
  22. }
  23. return buf;
  24. }
  25. function percentEncode(c) {
  26. let hex = c.toString(16).toUpperCase();
  27. if (hex.length === 1) {
  28. hex = "0" + hex;
  29. }
  30. return "%" + hex;
  31. }
  32. function percentDecode(input) {
  33. const output = Buffer.alloc(input.byteLength);
  34. let ptr = 0;
  35. for (let i = 0; i < input.length; ++i) {
  36. if (input[i] !== 37 || !isASCIIHex(input[i + 1]) || !isASCIIHex(input[i + 2])) {
  37. output[ptr++] = input[i];
  38. } else {
  39. output[ptr++] = parseInt(input.slice(i + 1, i + 3).toString(), 16);
  40. i += 2;
  41. }
  42. }
  43. return output.slice(0, ptr);
  44. }
  45. function parseUrlencoded(input) {
  46. const sequences = strictlySplitByteSequence(input, 38);
  47. const output = [];
  48. for (const bytes of sequences) {
  49. if (bytes.length === 0) {
  50. continue;
  51. }
  52. let name;
  53. let value;
  54. const indexOfEqual = bytes.indexOf(61);
  55. if (indexOfEqual >= 0) {
  56. name = bytes.slice(0, indexOfEqual);
  57. value = bytes.slice(indexOfEqual + 1);
  58. } else {
  59. name = bytes;
  60. value = Buffer.alloc(0);
  61. }
  62. name = replaceByteInByteSequence(Buffer.from(name), 43, 32);
  63. value = replaceByteInByteSequence(Buffer.from(value), 43, 32);
  64. output.push([percentDecode(name).toString(), percentDecode(value).toString()]);
  65. }
  66. return output;
  67. }
  68. function serializeUrlencodedByte(input) {
  69. let output = "";
  70. for (const byte of input) {
  71. if (byte === 32) {
  72. output += "+";
  73. } else if (byte === 42 ||
  74. byte === 45 ||
  75. byte === 46 ||
  76. (byte >= 48 && byte <= 57) ||
  77. (byte >= 65 && byte <= 90) ||
  78. byte === 95 ||
  79. (byte >= 97 && byte <= 122)) {
  80. output += String.fromCodePoint(byte);
  81. } else {
  82. output += percentEncode(byte);
  83. }
  84. }
  85. return output;
  86. }
  87. function serializeUrlencoded(tuples, encodingOverride = undefined) {
  88. let encoding = "utf-8";
  89. if (encodingOverride !== undefined) {
  90. encoding = encodingOverride;
  91. }
  92. let output = "";
  93. for (const [i, tuple] of tuples.entries()) {
  94. // TODO: handle encoding override
  95. const name = serializeUrlencodedByte(Buffer.from(tuple[0]));
  96. let value = tuple[1];
  97. if (tuple.length > 2 && tuple[2] !== undefined) {
  98. if (tuple[2] === "hidden" && name === "_charset_") {
  99. value = encoding;
  100. } else if (tuple[2] === "file") {
  101. // value is a File object
  102. value = value.name;
  103. }
  104. }
  105. value = serializeUrlencodedByte(Buffer.from(value));
  106. if (i !== 0) {
  107. output += "&";
  108. }
  109. output += `${name}=${value}`;
  110. }
  111. return output;
  112. }
  113. module.exports = {
  114. percentEncode,
  115. percentDecode,
  116. // application/x-www-form-urlencoded string parser
  117. parseUrlencoded(input) {
  118. return parseUrlencoded(Buffer.from(input));
  119. },
  120. // application/x-www-form-urlencoded serializer
  121. serializeUrlencoded
  122. };