UNPKG

@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
"use strict"; 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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