139 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			139 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "use strict";
 | |
| const { isASCIIHex } = require("./infra");
 | |
| 
 | |
| function strictlySplitByteSequence(buf, cp) {
 | |
|   const list = [];
 | |
|   let last = 0;
 | |
|   let i = buf.indexOf(cp);
 | |
|   while (i >= 0) {
 | |
|     list.push(buf.slice(last, i));
 | |
|     last = i + 1;
 | |
|     i = buf.indexOf(cp, last);
 | |
|   }
 | |
|   if (last !== buf.length) {
 | |
|     list.push(buf.slice(last));
 | |
|   }
 | |
|   return list;
 | |
| }
 | |
| 
 | |
| function replaceByteInByteSequence(buf, from, to) {
 | |
|   let i = buf.indexOf(from);
 | |
|   while (i >= 0) {
 | |
|     buf[i] = to;
 | |
|     i = buf.indexOf(from, i + 1);
 | |
|   }
 | |
|   return buf;
 | |
| }
 | |
| 
 | |
| function percentEncode(c) {
 | |
|   let hex = c.toString(16).toUpperCase();
 | |
|   if (hex.length === 1) {
 | |
|     hex = "0" + hex;
 | |
|   }
 | |
| 
 | |
|   return "%" + hex;
 | |
| }
 | |
| 
 | |
| function percentDecode(input) {
 | |
|   const output = Buffer.alloc(input.byteLength);
 | |
|   let ptr = 0;
 | |
|   for (let i = 0; i < input.length; ++i) {
 | |
|     if (input[i] !== 37 || !isASCIIHex(input[i + 1]) || !isASCIIHex(input[i + 2])) {
 | |
|       output[ptr++] = input[i];
 | |
|     } else {
 | |
|       output[ptr++] = parseInt(input.slice(i + 1, i + 3).toString(), 16);
 | |
|       i += 2;
 | |
|     }
 | |
|   }
 | |
|   return output.slice(0, ptr);
 | |
| }
 | |
| 
 | |
| function parseUrlencoded(input) {
 | |
|   const sequences = strictlySplitByteSequence(input, 38);
 | |
|   const output = [];
 | |
|   for (const bytes of sequences) {
 | |
|     if (bytes.length === 0) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     let name;
 | |
|     let value;
 | |
|     const indexOfEqual = bytes.indexOf(61);
 | |
| 
 | |
|     if (indexOfEqual >= 0) {
 | |
|       name = bytes.slice(0, indexOfEqual);
 | |
|       value = bytes.slice(indexOfEqual + 1);
 | |
|     } else {
 | |
|       name = bytes;
 | |
|       value = Buffer.alloc(0);
 | |
|     }
 | |
| 
 | |
|     name = replaceByteInByteSequence(Buffer.from(name), 43, 32);
 | |
|     value = replaceByteInByteSequence(Buffer.from(value), 43, 32);
 | |
| 
 | |
|     output.push([percentDecode(name).toString(), percentDecode(value).toString()]);
 | |
|   }
 | |
|   return output;
 | |
| }
 | |
| 
 | |
| function serializeUrlencodedByte(input) {
 | |
|   let output = "";
 | |
|   for (const byte of input) {
 | |
|     if (byte === 32) {
 | |
|       output += "+";
 | |
|     } else if (byte === 42 ||
 | |
|                byte === 45 ||
 | |
|                byte === 46 ||
 | |
|                (byte >= 48 && byte <= 57) ||
 | |
|                (byte >= 65 && byte <= 90) ||
 | |
|                byte === 95 ||
 | |
|                (byte >= 97 && byte <= 122)) {
 | |
|       output += String.fromCodePoint(byte);
 | |
|     } else {
 | |
|       output += percentEncode(byte);
 | |
|     }
 | |
|   }
 | |
|   return output;
 | |
| }
 | |
| 
 | |
| function serializeUrlencoded(tuples, encodingOverride = undefined) {
 | |
|   let encoding = "utf-8";
 | |
|   if (encodingOverride !== undefined) {
 | |
|     encoding = encodingOverride;
 | |
|   }
 | |
| 
 | |
|   let output = "";
 | |
|   for (const [i, tuple] of tuples.entries()) {
 | |
|     // TODO: handle encoding override
 | |
|     const name = serializeUrlencodedByte(Buffer.from(tuple[0]));
 | |
|     let value = tuple[1];
 | |
|     if (tuple.length > 2 && tuple[2] !== undefined) {
 | |
|       if (tuple[2] === "hidden" && name === "_charset_") {
 | |
|         value = encoding;
 | |
|       } else if (tuple[2] === "file") {
 | |
|         // value is a File object
 | |
|         value = value.name;
 | |
|       }
 | |
|     }
 | |
|     value = serializeUrlencodedByte(Buffer.from(value));
 | |
|     if (i !== 0) {
 | |
|       output += "&";
 | |
|     }
 | |
|     output += `${name}=${value}`;
 | |
|   }
 | |
|   return output;
 | |
| }
 | |
| 
 | |
| module.exports = {
 | |
|   percentEncode,
 | |
|   percentDecode,
 | |
| 
 | |
|   // application/x-www-form-urlencoded string parser
 | |
|   parseUrlencoded(input) {
 | |
|     return parseUrlencoded(Buffer.from(input));
 | |
|   },
 | |
| 
 | |
|   // application/x-www-form-urlencoded serializer
 | |
|   serializeUrlencoded
 | |
| };
 |