[CRX] Use DNR instead of webRequest in preserve-referer
webRequestBlocking is unavailable in MV3. Non-blocking webRequest can still be used to detect the Referer, but we have to use declarativeNetRequest to change the Referer header as needed.
This commit is contained in:
parent
7494dbccf4
commit
bd3d993180
@ -10,6 +10,7 @@
|
|||||||
"16": "icon16.png"
|
"16": "icon16.png"
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
"declarativeNetRequestWithHostAccess",
|
||||||
"webRequest",
|
"webRequest",
|
||||||
"webRequestBlocking",
|
"webRequestBlocking",
|
||||||
"<all_urls>",
|
"<all_urls>",
|
||||||
|
|||||||
@ -13,7 +13,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
/* globals saveReferer */
|
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
@ -139,9 +138,6 @@ chrome.webRequest.onHeadersReceived.addListener(
|
|||||||
|
|
||||||
var viewerUrl = getViewerURL(details.url);
|
var viewerUrl = getViewerURL(details.url);
|
||||||
|
|
||||||
// Implemented in preserve-referer.js
|
|
||||||
saveReferer(details);
|
|
||||||
|
|
||||||
return { redirectUrl: viewerUrl };
|
return { redirectUrl: viewerUrl };
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -13,20 +13,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
/* globals getHeaderFromHeaders */
|
|
||||||
/* exported saveReferer */
|
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
/**
|
/**
|
||||||
* This file is one part of the Referer persistency implementation. The other
|
* This file is one part of the Referer persistency implementation. The other
|
||||||
* part resides in chromecom.js.
|
* part resides in chromecom.js.
|
||||||
*
|
*
|
||||||
* This file collects request headers for every http(s) request, and temporarily
|
* This file collects Referer headers for every http(s) request, and temporarily
|
||||||
* stores the request headers in a dictionary. Upon completion of the request
|
* stores the request headers in a dictionary, for REFERRER_IN_MEMORY_TIME ms.
|
||||||
* (success or failure), the headers are discarded.
|
|
||||||
* pdfHandler.js will call saveReferer(details) when it is about to redirect to
|
|
||||||
* the viewer. Upon calling saveReferer, the Referer header is extracted from
|
|
||||||
* the request headers and saved.
|
|
||||||
*
|
*
|
||||||
* When the viewer is opened, it opens a port ("chromecom-referrer"). This port
|
* When the viewer is opened, it opens a port ("chromecom-referrer"). This port
|
||||||
* is used to set up the webRequest listeners that stick the Referer headers to
|
* is used to set up the webRequest listeners that stick the Referer headers to
|
||||||
@ -36,50 +30,38 @@ limitations under the License.
|
|||||||
* See setReferer in chromecom.js for more explanation of this logic.
|
* See setReferer in chromecom.js for more explanation of this logic.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Remembers the request headers for every http(s) page request for the duration
|
|
||||||
// of the request.
|
|
||||||
var g_requestHeaders = {};
|
|
||||||
// g_referrers[tabId][frameId] = referrer of PDF frame.
|
// g_referrers[tabId][frameId] = referrer of PDF frame.
|
||||||
var g_referrers = {};
|
var g_referrers = {};
|
||||||
|
var g_referrerTimers = {};
|
||||||
|
// The background script will eventually suspend after 30 seconds of inactivity.
|
||||||
|
// This can be delayed when extension events are firing. To prevent the data
|
||||||
|
// from being kept in memory for too long, cap the data duration to 5 minutes.
|
||||||
|
var REFERRER_IN_MEMORY_TIME = 300000;
|
||||||
|
|
||||||
(function () {
|
var rIsReferer = /^referer$/i;
|
||||||
var requestFilter = {
|
chrome.webRequest.onSendHeaders.addListener(
|
||||||
urls: ["*://*/*"],
|
function saveReferer(details) {
|
||||||
types: ["main_frame", "sub_frame"],
|
const { tabId, frameId, requestHeaders } = details;
|
||||||
};
|
g_referrers[tabId] ??= {};
|
||||||
chrome.webRequest.onSendHeaders.addListener(
|
g_referrers[tabId][frameId] = requestHeaders.find(h =>
|
||||||
function (details) {
|
rIsReferer.test(h.name)
|
||||||
g_requestHeaders[details.requestId] = details.requestHeaders;
|
)?.value;
|
||||||
},
|
forgetReferrerEventually(tabId);
|
||||||
requestFilter,
|
},
|
||||||
["requestHeaders", "extraHeaders"]
|
{ urls: ["*://*/*"], types: ["main_frame", "sub_frame"] },
|
||||||
);
|
["requestHeaders", "extraHeaders"]
|
||||||
chrome.webRequest.onBeforeRedirect.addListener(forgetHeaders, requestFilter);
|
);
|
||||||
chrome.webRequest.onCompleted.addListener(forgetHeaders, requestFilter);
|
|
||||||
chrome.webRequest.onErrorOccurred.addListener(forgetHeaders, requestFilter);
|
|
||||||
function forgetHeaders(details) {
|
|
||||||
delete g_requestHeaders[details.requestId];
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
/**
|
function forgetReferrerEventually(tabId) {
|
||||||
* @param {object} details - onHeadersReceived event data.
|
if (g_referrerTimers[tabId]) {
|
||||||
*/
|
clearTimeout(g_referrerTimers[tabId]);
|
||||||
function saveReferer(details) {
|
|
||||||
var referer =
|
|
||||||
g_requestHeaders[details.requestId] &&
|
|
||||||
getHeaderFromHeaders(g_requestHeaders[details.requestId], "referer");
|
|
||||||
referer = (referer && referer.value) || "";
|
|
||||||
if (!g_referrers[details.tabId]) {
|
|
||||||
g_referrers[details.tabId] = {};
|
|
||||||
}
|
}
|
||||||
g_referrers[details.tabId][details.frameId] = referer;
|
g_referrerTimers[tabId] = setTimeout(() => {
|
||||||
|
delete g_referrers[tabId];
|
||||||
|
delete g_referrerTimers[tabId];
|
||||||
|
}, REFERRER_IN_MEMORY_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
chrome.tabs.onRemoved.addListener(function (tabId) {
|
|
||||||
delete g_referrers[tabId];
|
|
||||||
});
|
|
||||||
|
|
||||||
// This method binds a webRequest event handler which adds the Referer header
|
// This method binds a webRequest event handler which adds the Referer header
|
||||||
// to matching PDF resource requests (only if the Referer is non-empty). The
|
// to matching PDF resource requests (only if the Referer is non-empty). The
|
||||||
// handler is removed as soon as the PDF viewer frame is unloaded.
|
// handler is removed as soon as the PDF viewer frame is unloaded.
|
||||||
@ -89,8 +71,11 @@ chrome.runtime.onConnect.addListener(function onReceivePort(port) {
|
|||||||
}
|
}
|
||||||
var tabId = port.sender.tab.id;
|
var tabId = port.sender.tab.id;
|
||||||
var frameId = port.sender.frameId;
|
var frameId = port.sender.frameId;
|
||||||
|
var dnrRequestId;
|
||||||
|
|
||||||
// If the PDF is viewed for the first time, then the referer will be set here.
|
// If the PDF is viewed for the first time, then the referer will be set here.
|
||||||
|
// Note: g_referrers could be empty if the background script was suspended by
|
||||||
|
// the browser. In that case, chromecom.js may send us the referer (below).
|
||||||
var referer = (g_referrers[tabId] && g_referrers[tabId][frameId]) || "";
|
var referer = (g_referrers[tabId] && g_referrers[tabId][frameId]) || "";
|
||||||
port.onMessage.addListener(function (data) {
|
port.onMessage.addListener(function (data) {
|
||||||
// If the viewer was opened directly (without opening a PDF URL first), then
|
// If the viewer was opened directly (without opening a PDF URL first), then
|
||||||
@ -99,49 +84,49 @@ chrome.runtime.onConnect.addListener(function onReceivePort(port) {
|
|||||||
if (data.referer) {
|
if (data.referer) {
|
||||||
referer = data.referer;
|
referer = data.referer;
|
||||||
}
|
}
|
||||||
chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
|
dnrRequestId = data.dnrRequestId;
|
||||||
if (referer) {
|
setStickyReferrer(dnrRequestId, tabId, data.requestUrl, referer, () => {
|
||||||
// Only add a blocking request handler if the referer has to be rewritten.
|
// Acknowledge the message, and include the latest referer for this frame.
|
||||||
chrome.webRequest.onBeforeSendHeaders.addListener(
|
port.postMessage(referer);
|
||||||
onBeforeSendHeaders,
|
});
|
||||||
{
|
|
||||||
urls: [data.requestUrl],
|
|
||||||
types: ["xmlhttprequest"],
|
|
||||||
tabId,
|
|
||||||
},
|
|
||||||
["blocking", "requestHeaders", "extraHeaders"]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Acknowledge the message, and include the latest referer for this frame.
|
|
||||||
port.postMessage(referer);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// The port is only disconnected when the other end reloads.
|
// The port is only disconnected when the other end reloads.
|
||||||
port.onDisconnect.addListener(function () {
|
port.onDisconnect.addListener(function () {
|
||||||
if (g_referrers[tabId]) {
|
unsetStickyReferrer(dnrRequestId);
|
||||||
delete g_referrers[tabId][frameId];
|
|
||||||
}
|
|
||||||
chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function onBeforeSendHeaders(details) {
|
|
||||||
if (details.frameId !== frameId) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
var headers = details.requestHeaders;
|
|
||||||
var refererHeader = getHeaderFromHeaders(headers, "referer");
|
|
||||||
if (!refererHeader) {
|
|
||||||
refererHeader = { name: "Referer" };
|
|
||||||
headers.push(refererHeader);
|
|
||||||
} else if (
|
|
||||||
refererHeader.value &&
|
|
||||||
refererHeader.value.lastIndexOf("chrome-extension:", 0) !== 0
|
|
||||||
) {
|
|
||||||
// Sanity check. If the referer is set, and the value is not the URL of
|
|
||||||
// this extension, then the request was not initiated by this extension.
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
refererHeader.value = referer;
|
|
||||||
return { requestHeaders: headers };
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setStickyReferrer(dnrRequestId, tabId, url, referer, callback) {
|
||||||
|
if (!referer) {
|
||||||
|
unsetStickyReferrer(dnrRequestId);
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rule = {
|
||||||
|
id: dnrRequestId,
|
||||||
|
condition: {
|
||||||
|
urlFilter: `|${url}|`,
|
||||||
|
// The viewer and background are presumed to have the same origin:
|
||||||
|
initiatorDomains: [location.hostname], // = chrome.runtime.id.
|
||||||
|
resourceTypes: ["xmlhttprequest"],
|
||||||
|
tabIds: [tabId],
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: "modifyHeaders",
|
||||||
|
requestHeaders: [{ operation: "set", header: "referer", value: referer }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
chrome.declarativeNetRequest.updateSessionRules(
|
||||||
|
{ removeRuleIds: [dnrRequestId], addRules: [rule] },
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsetStickyReferrer(dnrRequestId) {
|
||||||
|
if (dnrRequestId) {
|
||||||
|
chrome.declarativeNetRequest.updateSessionRules({
|
||||||
|
removeRuleIds: [dnrRequestId],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -267,6 +267,7 @@ if (window === top) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dnrRequestId;
|
||||||
// This port is used for several purposes:
|
// This port is used for several purposes:
|
||||||
// 1. When disconnected, the background page knows that the frame has unload.
|
// 1. When disconnected, the background page knows that the frame has unload.
|
||||||
// 2. When the referrer was saved in history.state.chromecomState, it is sent
|
// 2. When the referrer was saved in history.state.chromecomState, it is sent
|
||||||
@ -281,6 +282,7 @@ let port;
|
|||||||
// 3. Background -> page: Send latest referer and save to history.
|
// 3. Background -> page: Send latest referer and save to history.
|
||||||
// 4. Page: Invoke callback.
|
// 4. Page: Invoke callback.
|
||||||
function setReferer(url, callback) {
|
function setReferer(url, callback) {
|
||||||
|
dnrRequestId ??= crypto.getRandomValues(new Uint32Array(1))[0] % 0x80000000;
|
||||||
if (!port) {
|
if (!port) {
|
||||||
// The background page will accept the port, and keep adding the Referer
|
// The background page will accept the port, and keep adding the Referer
|
||||||
// request header to requests to |url| until the port is disconnected.
|
// request header to requests to |url| until the port is disconnected.
|
||||||
@ -290,6 +292,7 @@ function setReferer(url, callback) {
|
|||||||
port.onMessage.addListener(onMessage);
|
port.onMessage.addListener(onMessage);
|
||||||
// Initiate the information exchange.
|
// Initiate the information exchange.
|
||||||
port.postMessage({
|
port.postMessage({
|
||||||
|
dnrRequestId,
|
||||||
referer: window.history.state?.chromecomState,
|
referer: window.history.state?.chromecomState,
|
||||||
requestUrl: url,
|
requestUrl: url,
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user