mirror of
https://github.com/gorhill/uMatrix.git
synced 2024-06-28 02:50:39 +12:00
sanitizing outgoing headers (drafty)
This commit is contained in:
parent
5106c4bc44
commit
595de33e83
|
@ -614,7 +614,79 @@ vAPI.net = {};
|
|||
vAPI.net.registerListeners = function() {
|
||||
var µm = µMatrix;
|
||||
var µmuri = µm.URI;
|
||||
var httpRequestHeadersJunkyard = [];
|
||||
|
||||
// Abstraction layer to deal with request headers
|
||||
// >>>>>>>>
|
||||
var httpRequestHeadersFactory = function(headers) {
|
||||
var entry = httpRequestHeadersJunkyard.pop();
|
||||
if ( entry ) {
|
||||
return entry.init(headers);
|
||||
}
|
||||
return new HTTPRequestHeaders(headers);
|
||||
};
|
||||
|
||||
var HTTPRequestHeaders = function(headers) {
|
||||
this.init(headers);
|
||||
};
|
||||
|
||||
HTTPRequestHeaders.prototype.init = function(headers) {
|
||||
this.modified = false;
|
||||
this.headers = headers;
|
||||
return this;
|
||||
};
|
||||
|
||||
HTTPRequestHeaders.prototype.dispose = function() {
|
||||
var r = this.modified ? this.headers : null;
|
||||
this.headers = null;
|
||||
httpRequestHeadersJunkyard.push(this);
|
||||
return r;
|
||||
};
|
||||
|
||||
HTTPRequestHeaders.prototype.getHeader = function(target) {
|
||||
var headers = this.headers;
|
||||
var header, name;
|
||||
var i = headers.length;
|
||||
while ( i-- ) {
|
||||
header = headers[i];
|
||||
name = header.name.toLowerCase();
|
||||
if ( name === target ) {
|
||||
return header.value;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
HTTPRequestHeaders.prototype.setHeader = function(target, value, create) {
|
||||
var headers = this.headers;
|
||||
var header, name;
|
||||
var i = headers.length;
|
||||
while ( i-- ) {
|
||||
header = headers[i];
|
||||
name = header.name.toLowerCase();
|
||||
if ( name === target ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( i < 0 && !create ) { // Header not found, don't add it
|
||||
return false;
|
||||
}
|
||||
if ( i < 0 ) { // Header not found, add it
|
||||
headers.push({ name: target, value: value });
|
||||
} else if ( value === '' ) { // Header found, remove it
|
||||
headers.splice(i, 1);
|
||||
} else { // Header found, modify it
|
||||
header.value = value;
|
||||
}
|
||||
this.modified = true;
|
||||
return true;
|
||||
};
|
||||
// <<<<<<<<
|
||||
// End of: Abstraction layer to deal with request headers
|
||||
|
||||
|
||||
// Normalizing request types
|
||||
// >>>>>>>>
|
||||
var normalizeRequestDetails = function(details) {
|
||||
µmuri.set(details.url);
|
||||
|
||||
|
@ -651,7 +723,12 @@ vAPI.net.registerListeners = function() {
|
|||
// https://code.google.com/p/chromium/issues/detail?id=410382
|
||||
details.type = 'object';
|
||||
};
|
||||
// <<<<<<<<
|
||||
// End of: Normalizing request types
|
||||
|
||||
|
||||
// Network event handlers
|
||||
// >>>>>>>>
|
||||
var onBeforeRequestClient = this.onBeforeRequest.callback;
|
||||
var onBeforeRequest = function(details) {
|
||||
normalizeRequestDetails(details);
|
||||
|
@ -675,7 +752,15 @@ vAPI.net.registerListeners = function() {
|
|||
var onBeforeSendHeadersClient = this.onBeforeSendHeaders.callback;
|
||||
var onBeforeSendHeaders = function(details) {
|
||||
normalizeRequestDetails(details);
|
||||
return onBeforeSendHeadersClient(details);
|
||||
details.requestHeaders = httpRequestHeadersFactory(details.requestHeaders);
|
||||
var result = onBeforeSendHeadersClient(details);
|
||||
if ( typeof result === 'object' ) {
|
||||
return result;
|
||||
}
|
||||
var modifiedHeaders = details.requestHeaders.dispose();
|
||||
if ( modifiedHeaders !== null ) {
|
||||
return { requestHeaders: modifiedHeaders };
|
||||
}
|
||||
};
|
||||
chrome.webRequest.onBeforeSendHeaders.addListener(
|
||||
onBeforeSendHeaders,
|
||||
|
@ -706,6 +791,8 @@ vAPI.net.registerListeners = function() {
|
|||
'urls': this.onErrorOccurred.urls || ['<all_urls>']
|
||||
}
|
||||
);
|
||||
// <<<<<<<<
|
||||
// End of: Network event handlers
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
@ -948,6 +948,52 @@ CallbackWrapper.prototype.proxy = function(response) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
var httpRequestHeadersFactory = function(channel) {
|
||||
var entry = httpRequestHeadersFactory.junkyard.pop();
|
||||
if ( entry ) {
|
||||
return entry.init(channel);
|
||||
}
|
||||
return new HTTPRequestHeaders(channel);
|
||||
};
|
||||
|
||||
httpRequestHeadersFactory.junkyard = [];
|
||||
|
||||
var HTTPRequestHeaders = function(channel) {
|
||||
this.init(channel);
|
||||
};
|
||||
|
||||
HTTPRequestHeaders.prototype.init = function(channel) {
|
||||
this.channel = channel;
|
||||
return this;
|
||||
};
|
||||
|
||||
HTTPRequestHeaders.prototype.dispose = function() {
|
||||
this.channel = null;
|
||||
httpRequestHeadersFactory.junkyard.push(this);
|
||||
};
|
||||
|
||||
HTTPRequestHeaders.prototype.getHeader = function(name) {
|
||||
try {
|
||||
return this.channel.getRequestHeader(name);
|
||||
} catch (e) {
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
HTTPRequestHeaders.prototype.setHeader = function(name, newValue, create) {
|
||||
var oldValue = this.getHeader(name);
|
||||
if ( newValue === oldValue ) {
|
||||
return false;
|
||||
}
|
||||
if ( oldValue === '' && create !== true ) {
|
||||
return false;
|
||||
}
|
||||
this.channel.setRequestHeader(name, newValue, false);
|
||||
return true;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var httpObserver = {
|
||||
classDescription: 'net-channel-event-sinks for ' + location.host,
|
||||
classID: Components.ID('{dc8d6319-5f6e-4438-999e-53722db99e84}'),
|
||||
|
@ -967,9 +1013,11 @@ var httpObserver = {
|
|||
5: 'object',
|
||||
6: 'main_frame',
|
||||
7: 'sub_frame',
|
||||
10: 'ping',
|
||||
11: 'xmlhttprequest',
|
||||
12: 'object',
|
||||
14: 'font',
|
||||
16: 'websocket',
|
||||
21: 'image'
|
||||
},
|
||||
lastRequest: [{}, {}],
|
||||
|
@ -1061,41 +1109,47 @@ var httpObserver = {
|
|||
},
|
||||
|
||||
handleRequest: function(channel, URI, details) {
|
||||
var onBeforeRequest = vAPI.net.onBeforeRequest;
|
||||
var type = this.typeMap[details.type] || 'other';
|
||||
|
||||
if (
|
||||
onBeforeRequest.types.size !== 0 &&
|
||||
onBeforeRequest.types.has(type) === false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = onBeforeRequest.callback({
|
||||
var result;
|
||||
var callbackDetails = {
|
||||
frameId: details.frameId,
|
||||
hostname: URI.asciiHost,
|
||||
parentFrameId: details.parentFrameId,
|
||||
tabId: details.tabId,
|
||||
type: type,
|
||||
url: URI.asciiSpec
|
||||
});
|
||||
};
|
||||
|
||||
if ( !result || typeof result !== 'object' ) {
|
||||
return false;
|
||||
var onBeforeRequest = vAPI.net.onBeforeRequest;
|
||||
if ( onBeforeRequest.types.size === 0 || onBeforeRequest.types.has(type) ) {
|
||||
result = onBeforeRequest.callback(callbackDetails);
|
||||
|
||||
if ( typeof result === 'object' && result.cancel === true ) {
|
||||
channel.cancel(this.ABORT);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*if ( result.redirectUrl ) {
|
||||
channel.redirectionLimit = 1;
|
||||
channel.redirectTo(
|
||||
Services.io.newURI(result.redirectUrl, null, null)
|
||||
);
|
||||
return true;
|
||||
}*/
|
||||
}
|
||||
|
||||
if ( result.cancel === true ) {
|
||||
channel.cancel(this.ABORT);
|
||||
return true;
|
||||
}
|
||||
var onBeforeSendHeaders = vAPI.net.onBeforeSendHeaders;
|
||||
if ( onBeforeSendHeaders.types.size === 0 || onBeforeSendHeaders.types.has(type) ) {
|
||||
callbackDetails.requestHeaders = httpRequestHeadersFactory(channel);
|
||||
result = onBeforeSendHeaders.callback(callbackDetails);
|
||||
callbackDetails.requestHeaders.dispose();
|
||||
|
||||
/*if ( result.redirectUrl ) {
|
||||
channel.redirectionLimit = 1;
|
||||
channel.redirectTo(
|
||||
Services.io.newURI(result.redirectUrl, null, null)
|
||||
);
|
||||
return true;
|
||||
}*/
|
||||
if ( typeof result === 'object' && result.cancel === true ) {
|
||||
channel.cancel(this.ABORT);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
@ -1263,10 +1317,8 @@ vAPI.net = {};
|
|||
/******************************************************************************/
|
||||
|
||||
vAPI.net.registerListeners = function() {
|
||||
// Since it's not used
|
||||
this.onBeforeSendHeaders = null;
|
||||
|
||||
this.onBeforeRequest.types = new Set(this.onBeforeRequest.types);
|
||||
this.onBeforeSendHeaders.types = new Set(this.onBeforeSendHeaders.types);
|
||||
|
||||
var shouldLoadListenerMessageName = location.host + ':shouldLoad';
|
||||
var shouldLoadListener = function(e) {
|
||||
|
|
|
@ -272,7 +272,7 @@ var onBeforeRequestHandler = function(details) {
|
|||
|
||||
// console.debug('onBeforeRequestHandler()> "%s": %o', details.url, details);
|
||||
|
||||
var requestType = requestTypeNormalizer[details.type];
|
||||
var requestType = requestTypeNormalizer[details.type] || 'other';
|
||||
|
||||
// https://github.com/gorhill/httpswitchboard/issues/303
|
||||
// Wherever the main doc comes from, create a receiver page URL: synthetize
|
||||
|
@ -284,7 +284,7 @@ var onBeforeRequestHandler = function(details) {
|
|||
var requestURL = details.url;
|
||||
|
||||
// Is it µMatrix's noop css file?
|
||||
if ( requestType === 'css' && requestURL.slice(0, µm.noopCSSURL.length) === µm.noopCSSURL ) {
|
||||
if ( requestType === 'css' && requestURL.lastIndexOf(µm.noopCSSURL, 0) === 0 ) {
|
||||
return onBeforeChromeExtensionRequestHandler(details);
|
||||
}
|
||||
|
||||
|
@ -295,19 +295,12 @@ var onBeforeRequestHandler = function(details) {
|
|||
|
||||
// Do not block myself from updating assets
|
||||
// https://github.com/gorhill/httpswitchboard/issues/202
|
||||
if ( requestType === 'xhr' && requestURL.slice(0, µm.projectServerRoot.length) === µm.projectServerRoot ) {
|
||||
if ( requestType === 'xhr' && requestURL.lastIndexOf(µm.projectServerRoot, 0) === 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var requestHostname = µmuri.hostname;
|
||||
|
||||
// rhill 2013-12-15:
|
||||
// Try to transpose generic `other` category into something more
|
||||
// meaningful.
|
||||
if ( requestType === 'other' ) {
|
||||
requestType = µm.transposeType(requestType, µmuri.path);
|
||||
}
|
||||
|
||||
// Re-classify orphan HTTP requests as behind-the-scene requests. There is
|
||||
// not much else which can be done, because there are URLs
|
||||
// which cannot be handled by µMatrix, i.e. `opera://startpage`,
|
||||
|
@ -361,11 +354,7 @@ var onBeforeRequestHandler = function(details) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
// This is where tabless requests are processed, as here there may be a chance
|
||||
// we can bind a request to a specific tab, as headers may contain useful
|
||||
// information to accomplish this.
|
||||
//
|
||||
// Also we sanitize outgoing headers as per user settings.
|
||||
// Sanitize outgoing headers as per user settings.
|
||||
|
||||
var onBeforeSendHeadersHandler = function(details) {
|
||||
|
||||
|
@ -392,11 +381,27 @@ var onBeforeSendHeadersHandler = function(details) {
|
|||
// If yes, create a synthetic URL for reporting hyperlink auditing
|
||||
// in request log. This way the user is better informed of what went
|
||||
// on.
|
||||
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#hyperlink-auditing
|
||||
//
|
||||
// Target URL = the href of the link
|
||||
// Doc URL = URL of the document containing the target URL
|
||||
// Ping URLs = servers which will be told that user clicked target URL
|
||||
//
|
||||
// `Content-Type` = `text/ping` (always present)
|
||||
// `Ping-To` = target URL (always present)
|
||||
// `Ping-From` = doc URL
|
||||
// `Referer` = doc URL
|
||||
// request URL = URL which will receive the information
|
||||
//
|
||||
// With hyperlink-auditing, removing header(s) is pointless, the whole
|
||||
// request must be cancelled.
|
||||
|
||||
var requestURL = details.url;
|
||||
var requestType = requestTypeNormalizer[details.type];
|
||||
var requestType = requestTypeNormalizer[details.type] || 'other';
|
||||
if ( requestType === 'other' ) {
|
||||
var linkAuditor = hyperlinkAuditorFromHeaders(details.requestHeaders);
|
||||
if ( linkAuditor ) {
|
||||
var linkAuditor = details.requestHeaders.getHeader('ping-to');
|
||||
if ( linkAuditor !== '' ) {
|
||||
var block = µm.userSettings.processHyperlinkAuditing;
|
||||
pageStore.recordRequest('other', requestURL + '{Ping-To:' + linkAuditor + '}', block);
|
||||
µm.updateBadgeAsync(tabId);
|
||||
|
@ -411,123 +416,41 @@ var onBeforeSendHeadersHandler = function(details) {
|
|||
// is to sanitize headers.
|
||||
|
||||
var reqHostname = µm.hostnameFromURL(requestURL);
|
||||
var changed = false;
|
||||
|
||||
if ( µm.mustBlock(pageStore.pageHostname, reqHostname, 'cookie') ) {
|
||||
changed = foilCookieHeaders(µm, details) || changed;
|
||||
if ( details.requestHeaders.setHeader('cookie', '') ) {
|
||||
µm.cookieHeaderFoiledCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
if ( µm.tMatrix.evaluateSwitchZ('referrer-spoof', pageStore.pageHostname) ) {
|
||||
changed = foilRefererHeaders(µm, reqHostname, details) || changed;
|
||||
foilRefererHeaders(µm, reqHostname, details);
|
||||
}
|
||||
|
||||
if ( µm.tMatrix.evaluateSwitchZ('ua-spoof', pageStore.pageHostname) ) {
|
||||
changed = foilUserAgent(µm, details) || changed;
|
||||
// https://github.com/gorhill/httpswitchboard/issues/252
|
||||
// To avoid potential mismatch between the user agent from HTTP headers
|
||||
// and the user agent from subrequests and the window.navigator object,
|
||||
// I could always store here the effective user agent, but I am really
|
||||
// not convinced it is worth the added overhead given the low
|
||||
// probability and the benign consequence if it ever happen. Can always
|
||||
// be revised if ever I become aware a mismatch is a terrible thing
|
||||
details.requestHeaders.setHeader('user-agent', µm.userAgentReplaceStr);
|
||||
}
|
||||
|
||||
if ( changed ) {
|
||||
// console.debug('onBeforeSendHeadersHandler()> CHANGED "%s": %o', requestURL, details);
|
||||
return { requestHeaders: details.requestHeaders };
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#hyperlink-auditing
|
||||
//
|
||||
// Target URL = the href of the link
|
||||
// Doc URL = URL of the document containing the target URL
|
||||
// Ping URLs = servers which will be told that user clicked target URL
|
||||
//
|
||||
// `Content-Type` = `text/ping` (always present)
|
||||
// `Ping-To` = target URL (always present)
|
||||
// `Ping-From` = doc URL
|
||||
// `Referer` = doc URL
|
||||
// request URL = URL which will receive the information
|
||||
//
|
||||
// With hyperlink-auditing, removing header(s) is pointless, the whole
|
||||
// request must be cancelled.
|
||||
|
||||
var hyperlinkAuditorFromHeaders = function(headers) {
|
||||
var i = headers.length;
|
||||
while ( i-- ) {
|
||||
if ( headers[i].name.toLowerCase() === 'ping-to' ) {
|
||||
return headers[i].value;
|
||||
}
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var foilCookieHeaders = function(µm, details) {
|
||||
var changed = false;
|
||||
var headers = details.requestHeaders;
|
||||
var header;
|
||||
var i = headers.length;
|
||||
while ( i-- ) {
|
||||
header = headers[i];
|
||||
if ( header.name.toLowerCase() !== 'cookie' ) {
|
||||
continue;
|
||||
}
|
||||
// console.debug('foilCookieHeaders()> foiled browser attempt to send cookie(s) to "%s"', details.url);
|
||||
headers.splice(i, 1);
|
||||
µm.cookieHeaderFoiledCounter++;
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var foilRefererHeaders = function(µm, toHostname, details) {
|
||||
var headers = details.requestHeaders;
|
||||
var i = headers.length, header;
|
||||
while ( i-- ) {
|
||||
header = headers[i];
|
||||
if ( header.name.toLowerCase() === 'referer' ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( i === -1 ) {
|
||||
return false;
|
||||
var referer = details.requestHeaders.getHeader('referer');
|
||||
if ( referer === '' ) {
|
||||
return;
|
||||
}
|
||||
var µmuri = µm.URI;
|
||||
var fromDomain = µmuri.domainFromURI(header.value);
|
||||
var toDomain = µmuri.domainFromHostname(toHostname);
|
||||
if ( toDomain === fromDomain ) {
|
||||
return false;
|
||||
if ( µmuri.domainFromHostname(toHostname) === µmuri.domainFromURI(referer) ) {
|
||||
return;
|
||||
}
|
||||
//console.debug('foilRefererHeaders()> foiled referer for "%s"', details.url);
|
||||
//console.debug('\treferrer "%s"', header.value);
|
||||
// https://github.com/gorhill/httpswitchboard/issues/222#issuecomment-44828402
|
||||
header.value = µmuri.schemeFromURI(details.url) + '://' + toHostname + '/';
|
||||
//console.debug('\treplaced with "%s"', header.value);
|
||||
details.requestHeaders.setHeader(
|
||||
'referer',
|
||||
µmuri.schemeFromURI(details.url) + '://' + toHostname + '/'
|
||||
);
|
||||
µm.refererHeaderFoiledCounter++;
|
||||
return true;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var foilUserAgent = function(µm, details) {
|
||||
var headers = details.requestHeaders;
|
||||
var header;
|
||||
var i = 0;
|
||||
while ( header = headers[i] ) {
|
||||
if ( header.name.toLowerCase() === 'user-agent' ) {
|
||||
header.value = µm.userAgentReplaceStr;
|
||||
return true; // Assuming only one `user-agent` entry
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -549,7 +472,7 @@ var onHeadersReceived = function(details) {
|
|||
return;
|
||||
}
|
||||
|
||||
var requestType = requestTypeNormalizer[details.type];
|
||||
var requestType = requestTypeNormalizer[details.type] || 'other';
|
||||
if ( requestType === 'frame' ) {
|
||||
return onSubDocHeadersReceived(details);
|
||||
}
|
||||
|
@ -731,7 +654,7 @@ var onSubDocHeadersReceived = function(details) {
|
|||
|
||||
var onErrorOccurredHandler = function(details) {
|
||||
// console.debug('onErrorOccurred()> "%s": %o', details.url, details);
|
||||
var requestType = requestTypeNormalizer[details.type];
|
||||
var requestType = requestTypeNormalizer[details.type] || 'other';
|
||||
|
||||
// Ignore all that is not a main document
|
||||
if ( requestType !== 'doc'|| details.parentFrameId >= 0 ) {
|
||||
|
@ -788,7 +711,8 @@ var requestTypeNormalizer = {
|
|||
'image' : 'image',
|
||||
'object' : 'plugin',
|
||||
'xmlhttprequest': 'xhr',
|
||||
'other' : 'other'
|
||||
'other' : 'other',
|
||||
'font' : 'css'
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -808,10 +732,6 @@ vAPI.net.onBeforeSendHeaders = {
|
|||
"http://*/*",
|
||||
"https://*/*"
|
||||
],
|
||||
types: [
|
||||
"main_frame",
|
||||
"sub_frame"
|
||||
],
|
||||
extra: [ 'blocking', 'requestHeaders' ],
|
||||
callback: onBeforeSendHeadersHandler
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
echo "*** µMatrix(Chromium): Creating package"
|
||||
echo "*** µMatrix(Chromium): Copying files"
|
||||
|
||||
DES=./dist/uMatrix.chromium
|
||||
DES=./dist/build/uMatrix.chromium
|
||||
rm -rf $DES
|
||||
mkdir -p $DES
|
||||
|
||||
|
|
Loading…
Reference in a new issue