@reclaimprotocol/attestor-core
Version:
<div> <div> <img src="https://raw.githubusercontent.com/reclaimprotocol/.github/main/assets/banners/Attestor-Core.png" /> </div> </div>
251 lines • 19.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeHttpResponseParser = makeHttpResponseParser;
exports.getHttpRequestDataFromTranscript = getHttpRequestDataFromTranscript;
const tls_1 = require("@reclaimprotocol/tls");
const generics_1 = require("../utils/generics");
const redactions_1 = require("../utils/redactions");
const HTTP_HEADER_LINE_END = (0, tls_1.strToUint8Array)('\r\n');
/**
* parses http/1.1 responses
*/
function makeHttpResponseParser() {
/** the HTTP response data */
const res = {
statusCode: 0,
statusMessage: '',
headers: {},
body: new Uint8Array(),
complete: false,
headersComplete: false,
headerIndices: new Map(),
headerEndIdx: 0
};
let remainingBodyBytes = 0;
let isChunked = false;
let remaining = new Uint8Array();
let currentByteIdx = 0;
return {
res,
/**
* Parse the next chunk of data
* @param data the data to parse
*/
onChunk(data) {
var _a, _b;
// concatenate the remaining data from the last chunk
remaining = (0, tls_1.concatenateUint8Arrays)([remaining, data]);
// if we don't have the headers yet, keep reading lines
// as each header is in a line
if (!res.headersComplete) {
for (let line = getLine(); typeof line !== 'undefined'; line = getLine()) {
// first line is the HTTP version, status code & message
if (!res.statusCode) {
const [, statusCode, statusMessage] = line.match(/HTTP\/\d\.\d (\d+) (.*)/) || [];
res.statusCode = Number(statusCode);
res.statusMessage = statusMessage;
res.statusLineEndIndex = currentByteIdx - HTTP_HEADER_LINE_END.length;
}
else if (line === '') { // empty line signifies end of headers
res.headersComplete = true;
res.headerEndIdx = currentByteIdx - 4;
// if the response is chunked, we need to process the body differently
if ((_a = res.headers['transfer-encoding']) === null || _a === void 0 ? void 0 : _a.includes('chunked')) {
isChunked = true;
res.chunks = [];
break;
// if the response has a content-length, we know how many bytes to read
}
else if (res.headers['content-length']) {
remainingBodyBytes = Number(res.headers['content-length']);
break;
}
else {
remainingBodyBytes = -1;
// otherwise,
// no content-length, no chunked transfer encoding
// means wait till the stream ends
// https://stackoverflow.com/a/11376887
}
}
else if (!res.complete) { // parse the header
const [key, value] = line.split(': ');
res.headers[key.toLowerCase()] = value;
res.headerIndices[key.toLowerCase()] = {
fromIndex: currentByteIdx - line.length - HTTP_HEADER_LINE_END.length,
toIndex: currentByteIdx - HTTP_HEADER_LINE_END.length
};
}
else {
throw new Error('got more data after response was complete');
}
}
}
if (res.headersComplete) {
if (remainingBodyBytes) {
readBody();
// if no more body bytes to read,
// and the response was not chunked we're done
if (!remainingBodyBytes && !isChunked) {
res.complete = true;
}
}
if (res.headers['content-length'] === '0') {
res.complete = true;
}
if (isChunked) {
for (let line = getLine(); typeof line !== 'undefined'; line = getLine()) {
if (line === '') {
continue;
}
const chunkSize = Number.parseInt(line, 16);
// if chunk size is 0, we're done
if (!chunkSize) {
res.complete = true;
continue;
}
(_b = res.chunks) === null || _b === void 0 ? void 0 : _b.push({
fromIndex: currentByteIdx,
toIndex: currentByteIdx + chunkSize,
});
// otherwise read the chunk
remainingBodyBytes = chunkSize;
readBody();
// if we read all the data we had,
// but there's still data left,
// break the loop and wait for the next chunk
if (remainingBodyBytes) {
break;
}
}
}
}
},
/**
* Call to prevent further parsing; indicating the end of the request
* Checks that the response is valid & complete, otherwise throws an error
*/
streamEnded() {
if (!res.headersComplete) {
throw new Error('stream ended before headers were complete');
}
if (remaining.length) {
throw new Error('stream ended with remaining data');
}
if (remainingBodyBytes > 0) {
throw new Error('stream ended before all body bytes were received');
}
res.complete = true;
}
};
function readBody() {
if (res.complete) {
throw new Error('got more data after response was complete');
}
if (!res.bodyStartIndex) {
res.bodyStartIndex = currentByteIdx;
}
let bytesToCopy;
if (remainingBodyBytes === -1) {
// all bytes are body bytes
bytesToCopy = remaining.length;
}
else {
// take the number of bytes we need to read, or the number of bytes remaining
// and append to the bytes of the body
bytesToCopy = Math.min(remainingBodyBytes, remaining.length);
remainingBodyBytes -= bytesToCopy;
}
res.body = (0, tls_1.concatenateUint8Arrays)([
res.body,
remaining.slice(0, bytesToCopy)
]);
remaining = remaining.slice(bytesToCopy);
currentByteIdx += bytesToCopy;
}
function getLine() {
// find end of line, if it exists
// otherwise return undefined
const idx = (0, generics_1.findIndexInUint8Array)(remaining, HTTP_HEADER_LINE_END);
if (idx === -1) {
return undefined;
}
const line = (0, generics_1.uint8ArrayToStr)(remaining.slice(0, idx));
remaining = remaining.slice(idx + HTTP_HEADER_LINE_END.length);
currentByteIdx += idx + HTTP_HEADER_LINE_END.length;
return line;
}
}
/**
* Read the HTTP request from a TLS receipt transcript.
* @param receipt the transcript to read from or application messages if they were extracted beforehand
* @returns the parsed HTTP request
*/
function getHttpRequestDataFromTranscript(receipt) {
const clientMsgs = receipt
.filter(s => s.sender === 'client');
// if the first message is redacted, we can't parse it
// as we don't know what the request was
if (clientMsgs[0].message[0] === redactions_1.REDACTION_CHAR_CODE) {
throw new Error('First client message request is redacted. Cannot parse');
}
const request = {
method: '',
url: '',
protocol: '',
headers: {}
};
let requestBuffer = (0, tls_1.concatenateUint8Arrays)(clientMsgs.map(m => m.message));
// keep reading lines until we get to the end of the headers
for (let line = getLine(); typeof line !== 'undefined'; line = getLine()) {
if (line === '') {
break;
}
if (!request.method) {
const [, method, url, protocol] = line.match(/(\w+) (.*) (.*)/) || [];
request.method = method.toLowerCase();
request.url = url;
request.protocol = protocol;
}
else {
let keyIdx = line.indexOf(':');
if (keyIdx === -1) {
keyIdx = line.length - 1;
}
const key = line.slice(0, keyIdx)
.toLowerCase()
.trim();
const value = line.slice(keyIdx + 1)
.trim();
const oldValue = request.headers[key];
if (typeof oldValue === 'string') {
request.headers[key] = [oldValue, value];
}
else if (Array.isArray(oldValue)) {
oldValue.push(value);
}
else {
request.headers[key] = value;
}
}
}
//the rest is request body
if (requestBuffer.length) {
request.body = requestBuffer;
}
if (!request.method) {
throw new Error('Client request is incomplete');
}
return request;
function getLine() {
const idx = (0, generics_1.findIndexInUint8Array)(requestBuffer, HTTP_HEADER_LINE_END);
if (idx === -1) {
return undefined;
}
const line = (0, generics_1.uint8ArrayToStr)(requestBuffer.slice(0, idx));
requestBuffer = requestBuffer
.slice(idx + HTTP_HEADER_LINE_END.length);
return line;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC1wYXJzZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvaHR0cC1wYXJzZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFrREEsd0RBbUxDO0FBT0QsNEVBeUVDO0FBclRELDhDQUE4RTtBQUc5RSxpREFBMkU7QUFDM0UscURBQTBEO0FBeUMxRCxNQUFNLG9CQUFvQixHQUFHLElBQUEscUJBQWUsRUFBQyxNQUFNLENBQUMsQ0FBQTtBQUVwRDs7R0FFRztBQUNILFNBQWdCLHNCQUFzQjtJQUNyQyw2QkFBNkI7SUFDN0IsTUFBTSxHQUFHLEdBQWlCO1FBQ3pCLFVBQVUsRUFBRSxDQUFDO1FBQ2IsYUFBYSxFQUFFLEVBQUU7UUFDakIsT0FBTyxFQUFFLEVBQUU7UUFDWCxJQUFJLEVBQUUsSUFBSSxVQUFVLEVBQUU7UUFDdEIsUUFBUSxFQUFFLEtBQUs7UUFDZixlQUFlLEVBQUUsS0FBSztRQUN0QixhQUFhLEVBQUMsSUFBSSxHQUFHLEVBQXNCO1FBQzNDLFlBQVksRUFBRSxDQUFDO0tBQ2YsQ0FBQTtJQUVELElBQUksa0JBQWtCLEdBQUcsQ0FBQyxDQUFBO0lBQzFCLElBQUksU0FBUyxHQUFHLEtBQUssQ0FBQTtJQUNyQixJQUFJLFNBQVMsR0FBRyxJQUFJLFVBQVUsRUFBRSxDQUFBO0lBQ2hDLElBQUksY0FBYyxHQUFHLENBQUMsQ0FBQTtJQUV0QixPQUFPO1FBQ04sR0FBRztRQUNIOzs7V0FHUztRQUNULE9BQU8sQ0FBQyxJQUFnQjs7WUFDdkIscURBQXFEO1lBQ3JELFNBQVMsR0FBRyxJQUFBLDRCQUFzQixFQUFDLENBQUMsU0FBUyxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUE7WUFDckQsdURBQXVEO1lBQ3ZELDhCQUE4QjtZQUM5QixJQUFHLENBQUMsR0FBRyxDQUFDLGVBQWUsRUFBRSxDQUFDO2dCQUN6QixLQUFJLElBQUksSUFBSSxHQUFHLE9BQU8sRUFBRSxFQUFFLE9BQU8sSUFBSSxLQUFLLFdB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
;