1
0
Fork 0
firefox/toolkit/components/captchadetection/CaptchaResponseObserver.sys.mjs
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

131 lines
3.7 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/** @type {lazy} */
const lazy = {};
ChromeUtils.defineLazyGetter(lazy, "console", () => {
return console.createInstance({
prefix: "CaptchaDetectionResponseObserver",
maxLogLevelPref: "captchadetection.loglevel",
});
});
ChromeUtils.defineESModuleGetters(lazy, {
CommonUtils: "resource://services-common/utils.sys.mjs",
});
/**
* This class is responsible for observing HTTP responses
*
* Do note that it won't work for cached responses.
* We don't need to handle cached responses because
* we are only interested in API responses from
* captcha services.
*/
export class CaptchaResponseObserver {
/**
* @param {function(nsIHttpChannel): boolean} shouldIntercept - A function that returns true if the response should be intercepted.
* @param {function(nsIHttpChannel, nsresult, string): void} onResponseBody - A function that is called when the response body is available.
*/
constructor(shouldIntercept, onResponseBody) {
/** @type {Map<nsIChannel, nsIPipe>} */
this.requestToTeePipe = new WeakMap();
this.shouldIntercept = shouldIntercept;
this.onResponseBody = onResponseBody;
}
register() {
Services.obs.addObserver(this, "http-on-examine-response");
}
unregister() {
Services.obs.removeObserver(this, "http-on-examine-response");
}
observe(channel) {
if (!(channel instanceof Ci.nsIHttpChannel)) {
return;
}
channel.QueryInterface(Ci.nsITraceableChannel);
if (!this.shouldIntercept(channel)) {
return;
}
const pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
pipe.init(false, false, 0, 0xffffffff, null);
const tee = Cc["@mozilla.org/network/stream-listener-tee;1"].createInstance(
Ci.nsIStreamListenerTee
);
const originalListener = channel.setNewListener(tee);
tee.init(originalListener, pipe.outputStream, this);
this.requestToTeePipe.set(channel, pipe);
}
onStartRequest() {}
/**
* @param {nsIHttpChannel} channel - The channel.
* @param {nsIXPCComponents_Values} statusCode - The status code, not to be confused with the HTTP status code.
*/
async onStopRequest(channel, statusCode) {
const pipe = this.requestToTeePipe.get(channel);
pipe.outputStream.close();
this.requestToTeePipe.delete(channel);
let length = 0;
try {
length = pipe.inputStream.available();
} catch (e) {
lazy.console.error("Error reading response body", e);
return;
}
let responseBody = "";
if (length) {
const sin = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
Ci.nsIScriptableInputStream
);
sin.init(pipe.inputStream);
responseBody = sin.readBytes(length);
sin.close();
}
if (
channel instanceof Ci.nsIEncodedChannel &&
channel.contentEncodings &&
!channel.applyConversion &&
!channel.hasContentDecompressed
) {
const encodingHeader = channel.getResponseHeader("Content-Encoding");
const encodings = encodingHeader.split(/\s*\t*,\s*\t*/);
for (const encoding of encodings) {
responseBody = lazy.CommonUtils.convertString(
responseBody,
encoding,
"uncompressed"
);
}
}
this.onResponseBody(channel, statusCode, responseBody);
}
QueryInterface = ChromeUtils.generateQI([
Ci.nsIObserver,
Ci.nsIRequestObserver,
]);
}
/**
* @typedef lazy
* @type {object}
* @property {ConsoleInstance} console - console instance.
* @property {typeof import("services/common/utils.sys.mjs").CommonUtils} CommonUtils - CommonUtils instance.
*/