This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
138 lines
3.3 KiB
138 lines
3.3 KiB
"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 |
|
};
|
|
|