在 Web Extension 中拦截请求
Erioifpud
3 min read
最近在开发一个浏览器插件,有个需求需要拦截指定的请求,拿到响应结果进行储存。
在我印象中插件是有请求拦截相关 API 的,问了 GPT,他信誓旦旦地和我说,可以用 WebRequest:
browser.webRequest.onBeforeRequest.addListener(details => {
console.log('[REQUEST]', details)
}, {
urls: ['<all_urls>'],
});
browser.webRequest.onCompleted.addListener(details => {
console.log('[RESPONSE]', details)
}, {
urls: ['<all_urls>'],
});
browser.webRequest.onErrorOccurred.addListener(details => {
console.log('[ERROR]', details)
}, {
urls: ['<all_urls>'],
});
看起来没问题,试了一下发现只能获取请求的基本信息,比如 url、参数、时间等,不行的原因也很简单,就是安全问题。
虽然官方 API 不行,但这个需求还是能做的,做起来的方法也很简单粗暴,就是 hack 原有的请求 API。
XMLHttpRequest
var script = document.createElement('script');
script.textContent = '(' + function() {
// Save the original XMLHttpRequest object
var originalXhr = window.XMLHttpRequest;
// Create a new XMLHttpRequest constructor
window.XMLHttpRequest = function() {
var xhr = new originalXhr();
// Save the original open and send functions
var originalOpen = xhr.open;
var originalSend = xhr.send;
// Override the open function to save the method and url
xhr.open = function(method, url) {
xhr._method = method;
xhr._url = url;
originalOpen.apply(this, arguments);
};
// Override the send function to save the request body
xhr.send = function(body) {
xhr._body = body;
// When the ready state changes, log the request and response
this.addEventListener('readystatechange', function() {
if (this.readyState === 4) { // 4 means the request is done
var id = generateId(); // Generate a unique ID for this request
var request = {
method: xhr._method,
url: xhr._url,
headers: parseRequestHeaders(xhr._requestHeaders),
body: xhr._body
};
var response = {
headers: parseResponseHeaders(this.getAllResponseHeaders()),
body: this.responseText
};
// Save the request and response to a map
window._requests = window._requests || {};
window._requests[id] = {
request: request,
response: response
};
console.log('Request', id, request);
console.log('Response', id, response);
}
}, false);
// Call the original send function
originalSend.apply(this, arguments);
};
// Override the setRequestHeader function to save the request headers
xhr.setRequestHeader = function(header, value) {
xhr._requestHeaders = xhr._requestHeaders || {};
xhr._requestHeaders[header] = value;
originalSetRequestHeader.apply(this, arguments);
};
return xhr;
};
// Generate a unique ID for each request
function generateId() {
return Math.random().toString(36).substr(2);
}
// Parse the request headers from an object to an array
function parseRequestHeaders(headers) {
var result = [];
for (var header in headers) {
if (headers.hasOwnProperty(header)) {
result.push({name: header, value: headers[header]});
}
}
return result;
}
// Parse the response headers from a string to an array
function parseResponseHeaders(headers) {
var result = [];
headers.trim().split(/[\\r\\n]+/).forEach(function(line) {
var parts = line.split(': ');
var header = parts.shift();
var value = parts.join(': ');
result.push({name: header, value: value});
});
return result;
}
} + ')();';
document.documentElement.appendChild(script);
Fetch
var script = document.createElement('script');
script.textContent = '(' + function() {
// Save the original fetch function
var originalFetch = window.fetch;
// Override the fetch function
window.fetch = function(input, init) {
// Generate a unique ID for this request
var id = generateId();
// Save the request info
var request = {
method: (init && init.method) || 'GET',
url: input instanceof Request ? input.url : input,
headers: (init && init.headers) || {},
body: (init && init.body) || null
};
// Call the original fetch function
return originalFetch.apply(this, arguments).then(function(response) {
// When the response is ready, clone it and read its body
var clone = response.clone();
clone.text().then(function(body) {
// Save the response info
var responseInfo = {
headers: parseResponseHeaders(response.headers),
body: body
};
// Save the request and response to a map
window._requests = window._requests || {};
window._requests[id] = {
request: request,
response: responseInfo
};
console.log('Request', id, request);
console.log('Response', id, responseInfo);
});
return response;
});
};
// Generate a unique ID for each request
function generateId() {
return Math.random().toString(36).substr(2);
}
// Parse the response headers from a Headers object to an array
function parseResponseHeaders(headers) {
var result = [];
headers.forEach(function(value, name) {
result.push({name: name, value: value});
});
return result;
}
} + ')();';
document.documentElement.appendChild(script);
大概是这样,直接对 XMLHttpRequest
或 fetch
动手(也可以修改 XMLHttpRequest.prototype.open
等)。
但要注意,这样做的风险不低:
有的网站自己的脚本也会修改这两个 API,插件可能破坏了网站的环境
有的网站可能对一些原生 API 做了校验,直接修改他们会被检测出来
还有一个问题,这样做能不能过商店审核呢?
0
Subscribe to my newsletter
Read articles from Erioifpud directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by