| 1 | "use strict";
|
|---|
| 2 | const { utf8Encode, utf8DecodeWithoutBOM } = require("./encoding");
|
|---|
| 3 | const { percentDecodeBytes, utf8PercentEncodeString, isURLEncodedPercentEncode } = require("./percent-encoding");
|
|---|
| 4 |
|
|---|
| 5 | // https://url.spec.whatwg.org/#concept-urlencoded-parser
|
|---|
| 6 | function parseUrlencoded(input) {
|
|---|
| 7 | const sequences = strictlySplitByteSequence(input, 38);
|
|---|
| 8 | const output = [];
|
|---|
| 9 | for (const bytes of sequences) {
|
|---|
| 10 | if (bytes.length === 0) {
|
|---|
| 11 | continue;
|
|---|
| 12 | }
|
|---|
| 13 |
|
|---|
| 14 | let name, value;
|
|---|
| 15 | const indexOfEqual = bytes.indexOf(61);
|
|---|
| 16 |
|
|---|
| 17 | if (indexOfEqual >= 0) {
|
|---|
| 18 | name = bytes.slice(0, indexOfEqual);
|
|---|
| 19 | value = bytes.slice(indexOfEqual + 1);
|
|---|
| 20 | } else {
|
|---|
| 21 | name = bytes;
|
|---|
| 22 | value = new Uint8Array(0);
|
|---|
| 23 | }
|
|---|
| 24 |
|
|---|
| 25 | name = replaceByteInByteSequence(name, 0x2B, 0x20);
|
|---|
| 26 | value = replaceByteInByteSequence(value, 0x2B, 0x20);
|
|---|
| 27 |
|
|---|
| 28 | const nameString = utf8DecodeWithoutBOM(percentDecodeBytes(name));
|
|---|
| 29 | const valueString = utf8DecodeWithoutBOM(percentDecodeBytes(value));
|
|---|
| 30 |
|
|---|
| 31 | output.push([nameString, valueString]);
|
|---|
| 32 | }
|
|---|
| 33 | return output;
|
|---|
| 34 | }
|
|---|
| 35 |
|
|---|
| 36 | // https://url.spec.whatwg.org/#concept-urlencoded-string-parser
|
|---|
| 37 | function parseUrlencodedString(input) {
|
|---|
| 38 | return parseUrlencoded(utf8Encode(input));
|
|---|
| 39 | }
|
|---|
| 40 |
|
|---|
| 41 | // https://url.spec.whatwg.org/#concept-urlencoded-serializer
|
|---|
| 42 | function serializeUrlencoded(tuples, encodingOverride = undefined) {
|
|---|
| 43 | let encoding = "utf-8";
|
|---|
| 44 | if (encodingOverride !== undefined) {
|
|---|
| 45 | // TODO "get the output encoding", i.e. handle encoding labels vs. names.
|
|---|
| 46 | encoding = encodingOverride;
|
|---|
| 47 | }
|
|---|
| 48 |
|
|---|
| 49 | let output = "";
|
|---|
| 50 | for (const [i, tuple] of tuples.entries()) {
|
|---|
| 51 | // TODO: handle encoding override
|
|---|
| 52 |
|
|---|
| 53 | const name = utf8PercentEncodeString(tuple[0], isURLEncodedPercentEncode, true);
|
|---|
| 54 |
|
|---|
| 55 | let value = tuple[1];
|
|---|
| 56 | if (tuple.length > 2 && tuple[2] !== undefined) {
|
|---|
| 57 | if (tuple[2] === "hidden" && name === "_charset_") {
|
|---|
| 58 | value = encoding;
|
|---|
| 59 | } else if (tuple[2] === "file") {
|
|---|
| 60 | // value is a File object
|
|---|
| 61 | value = value.name;
|
|---|
| 62 | }
|
|---|
| 63 | }
|
|---|
| 64 |
|
|---|
| 65 | value = utf8PercentEncodeString(value, isURLEncodedPercentEncode, true);
|
|---|
| 66 |
|
|---|
| 67 | if (i !== 0) {
|
|---|
| 68 | output += "&";
|
|---|
| 69 | }
|
|---|
| 70 | output += `${name}=${value}`;
|
|---|
| 71 | }
|
|---|
| 72 | return output;
|
|---|
| 73 | }
|
|---|
| 74 |
|
|---|
| 75 | function strictlySplitByteSequence(buf, cp) {
|
|---|
| 76 | const list = [];
|
|---|
| 77 | let last = 0;
|
|---|
| 78 | let i = buf.indexOf(cp);
|
|---|
| 79 | while (i >= 0) {
|
|---|
| 80 | list.push(buf.slice(last, i));
|
|---|
| 81 | last = i + 1;
|
|---|
| 82 | i = buf.indexOf(cp, last);
|
|---|
| 83 | }
|
|---|
| 84 | if (last !== buf.length) {
|
|---|
| 85 | list.push(buf.slice(last));
|
|---|
| 86 | }
|
|---|
| 87 | return list;
|
|---|
| 88 | }
|
|---|
| 89 |
|
|---|
| 90 | function replaceByteInByteSequence(buf, from, to) {
|
|---|
| 91 | let i = buf.indexOf(from);
|
|---|
| 92 | while (i >= 0) {
|
|---|
| 93 | buf[i] = to;
|
|---|
| 94 | i = buf.indexOf(from, i + 1);
|
|---|
| 95 | }
|
|---|
| 96 | return buf;
|
|---|
| 97 | }
|
|---|
| 98 |
|
|---|
| 99 | module.exports = {
|
|---|
| 100 | parseUrlencodedString,
|
|---|
| 101 | serializeUrlencoded
|
|---|
| 102 | };
|
|---|