Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- addEventListener('fetch', event => {
- event.respondWith(handleRequest(event.request))
- })
- /**
- * Fetch and log a request
- * @param {Request} request
- */
- async function handleRequest(request) {
- const response = await fetch(request);
- var ctype = response.headers.get('content-type');
- if (ctype.startsWith('text/html') === false)
- return response; //Only parse html body
- let { readable, writable } = new TransformStream();
- let promise = injectScript(response.body, writable, "<script>var test = 1;</script>");
- return new Response(readable, response);
- }
- async function injectScript(readable, writable, injectable) {
- let decoder = new TextDecoder('utf-8');
- let encoder = new TextEncoder('utf-8'); //By the way: once we start encoding we can't simply forward byte arrays anymore
- let reader = readable.getReader();
- let writer = writable.getWriter();
- let done = await parseForInjection(reader, writer, decoder, encoder, injectable);
- if (done) return; //Very edge case: Somehow </head> is never found?
- await forwardTheRest(reader, writer, decoder, encoder);
- }
- async function parseForInjection(reader, writer, decoder, encoder, injectable)
- {
- let textBuffer = '';
- for (;;) {
- let {value, done } = await reader.read();
- if (done) {
- if (textBuffer.length > 0) {
- //Very edge case: Somehow </head> is never found?
- let doneBytes = encoder.encode(textBuffer);
- await writer.write(doneBytes);
- textBuffer = '';
- }
- await writer.close();
- return true;
- }
- textBuffer += decoder.decode(value); //Once we start, we must always use encoder/decoder
- var foundInd = textBuffer.indexOf("</head>"); //Don't use regex, this is all we need and it's faster
- if (foundInd === -1)
- {
- let isLikelyHeadTagSplit = CheckIfHeadTagIsSplit(textBuffer);
- if (isLikelyHeadTagSplit)
- {
- continue; //Keep buiding up the textBuffer
- }
- else
- {
- //Not found:
- let theseBytes = encoder.encode(textBuffer);
- await writer.write(theseBytes);
- textBuffer = '';
- continue;
- }
- }
- //Found:
- await doInjection(textBuffer, foundInd, injectable, encoder, writer);
- //We don't need to clear textBuffer, it's about to go out of scope
- return false; //
- }
- }
- async function forwardTheRest(reader, writer, decoder, encoder)
- {
- for (;;) {
- let { value, done } = await reader.read()
- if (done) {
- await writer.close();
- return;
- }
- //Once we start, we must always use encoder/decoder
- let thisText = decoder.decode(value);
- let theseBytes = encoder.encode(thisText);
- await writer.write(theseBytes)
- }
- }
- function CheckIfHeadTagIsSplit(textBuffer) {
- //It's highly unlikely, so do a very basic probabilistic check first
- let lastChar = textBuffer[textBuffer.length - 1];
- let splitIsPossible = (lastChar === "<" ||
- lastChar === "/" ||
- lastChar === "h" ||
- lastChar === "e" ||
- lastChar === "a" ||
- lastChar === "d");
- if (splitIsPossible == false)
- {
- return false;
- }
- //It's likely, so now do a more exact test
- if (textBuffer.endsWith("<"))
- return true; //Maybe
- if (textBuffer.endsWith("</"))
- return true; //Maybe (could be </Script>)
- if (textBuffer.endsWith("</h"))
- return true; //Very Likely (I can't think of any tags for the head section other than "/head")
- if (textBuffer.endsWith("</he"))
- return true; //Super Likely
- if (textBuffer.endsWith("</hea"))
- return true; //Super Likely
- if (textBuffer.endsWith("</head"))
- return true; //Super likely
- return false;
- }
- async function doInjection(textBuffer, foundInd, injectable, encoder, writer)
- {
- let textBefore = textBuffer.substr(0, foundInd);
- let bytes = encoder.encode(textBefore);
- await writer.write(bytes);
- bytes = encoder.encode(injectable);
- await writer.write(bytes);
- let textAfter = textBuffer.substr(foundInd, textBuffer.length - foundInd);
- bytes = encoder.encode(textAfter);
- await writer.write(bytes);
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement