Skip to content

Commit

Permalink
Merge pull request #5823 from Rob--W/preserve-http-referer
Browse files Browse the repository at this point in the history
[Chrome extension] Add Referer request header if needed
  • Loading branch information
Rob--W committed Jun 5, 2015
2 parents d95b4e4 + adb2f8a commit 8aa1c81
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 3 deletions.
1 change: 1 addition & 0 deletions extensions/chromium/pdfHandler.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
-->
<script src="chrome.tabs.executeScriptInFrame.js"></script>
<script src="feature-detect.js"></script>
<script src="preserve-referer.js"></script>
<script src="pdfHandler.js"></script>
<script src="extension-router.js"></script>
<script src="pdfHandler-v2.js"></script>
Expand Down
5 changes: 4 additions & 1 deletion extensions/chromium/pdfHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* globals chrome, Features */
/* globals chrome, Features, saveReferer */

'use strict';

Expand Down Expand Up @@ -113,6 +113,9 @@ chrome.webRequest.onHeadersReceived.addListener(

var viewerUrl = getViewerURL(details.url);

// Implemented in preserve-referer.js
saveReferer(details);

// Replace frame with viewer
if (Features.webRequestRedirectUrl) {
return { redirectUrl: viewerUrl };
Expand Down
143 changes: 143 additions & 0 deletions extensions/chromium/preserve-referer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/*
Copyright 2015 Mozilla Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* globals chrome, getHeaderFromHeaders */
/* exported saveReferer */

'use strict';
/**
* This file is one part of the Referer persistency implementation. The other
* part resides in chromecom.js.
*
* This file collects request headers for every http(s) request, and temporarily
* stores the request headers in a dictionary. Upon completion of the request
* (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
* is used to set up the webRequest listeners that stick the Referer headers to
* the HTTP requests created by this extension. When the port is disconnected,
* the webRequest listeners and the referrer information is discarded.
*
* 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.
var g_referrers = {};

(function() {
var requestFilter = {
urls: ['*://*/*'],
types: ['main_frame', 'sub_frame']
};
chrome.webRequest.onSendHeaders.addListener(function(details) {
g_requestHeaders[details.requestId] = details.requestHeaders;
}, requestFilter, ['requestHeaders']);
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];
}
})();

/**
* @param {object} details - onHeadersReceived event data.
*/
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;
}

chrome.tabs.onRemoved.addListener(function(tabId) {
delete g_referrers[tabId];
});

// 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
// handler is removed as soon as the PDF viewer frame is unloaded.
chrome.runtime.onConnect.addListener(function onReceivePort(port) {
if (port.name !== 'chromecom-referrer') {
return;
}
// Note: sender.frameId is only set in Chrome 41+.
if (!('frameId' in port.sender)) {
port.disconnect();
return;
}
var tabId = port.sender.tab.id;
var frameId = port.sender.frameId;

// If the PDF is viewed for the first time, then the referer will be set here.
var referer = g_referrers[tabId] && g_referrers[tabId][frameId] || '';
port.onMessage.addListener(function(data) {
// If the viewer was opened directly (without opening a PDF URL first), then
// the background script does not know about g_referrers, but the viewer may
// know about the referer if stored in the history state (see chromecom.js).
if (data.referer) {
referer = data.referer;
}
chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
if (referer) {
// Only add a blocking request handler if the referer has to be rewritten.
chrome.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, {
urls: [data.requestUrl],
types: ['xmlhttprequest'],
tabId: tabId
}, ['blocking', 'requestHeaders']);
}
// Acknowledge the message, and include the latest referer for this frame.
port.postMessage(referer);
});

// The port is only disconnected when the other end reloads.
port.onDisconnect.addListener(function() {
if (g_referrers[tabId]) {
delete g_referrers[tabId][frameId];
}
chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
});

function onBeforeSendHeaders(details) {
if (details.frameId !== frameId) {
return;
}
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;
}
refererHeader.value = referer;
return {requestHeaders: headers};
}
});
62 changes: 62 additions & 0 deletions web/chromecom.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,70 @@ var ChromeCom = (function ChromeComClosure() {
});
return;
}
if (/^https?:/.test(file)) {
// Assumption: The file being opened is the file that was requested.
// There is no UI to input a different URL, so this assumption will hold
// for now.
setReferer(file, function() {
PDFViewerApplication.open(file, 0);
});
return;
}
PDFViewerApplication.open(file, 0);
});
};

// This port is used for several purposes:
// 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
// to the background page.
// 3. When the background page knows the referrer of the page, the referrer is
// saved in history.state.chromecomState.
var port;
// Set the referer for the given URL.
// 0. Background: If loaded via a http(s) URL: Save referer.
// 1. Page -> background: send URL and referer from history.state
// 2. Background: Bind referer to URL (via webRequest).
// 3. Background -> page: Send latest referer and save to history.
// 4. Page: Invoke callback.
function setReferer(url, callback) {
if (!port) {
// The background page will accept the port, and keep adding the Referer
// request header to requests to |url| until the port is disconnected.
port = chrome.runtime.connect({name: 'chromecom-referrer'});
}
port.onDisconnect.addListener(onDisconnect);
port.onMessage.addListener(onMessage);
// Initiate the information exchange.
port.postMessage({
referer: window.history.state && window.history.state.chromecomState,
requestUrl: url
});

function onMessage(referer) {
if (referer) {
// The background extracts the Referer from the initial HTTP request for
// the PDF file. When the viewer is reloaded or when the user navigates
// back and forward, the background page will not observe a HTTP request
// with Referer. To make sure that the Referer is preserved, store it in
// history.state, which is preserved accross reloads/navigations.
var state = window.history.state || {};
state.chromecomState = referer;
window.history.replaceState(state, '');
}
onCompleted();
}
function onDisconnect() {
// When the connection fails, ignore the error and call the callback.
port = null;
onCompleted();
}
function onCompleted() {
port.onDisconnect.removeListener(onDisconnect);
port.onMessage.removeListener(onMessage);
callback();
}
}

return ChromeCom;
})();
11 changes: 11 additions & 0 deletions web/pdf_history.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ var PDFHistory = (function () {
});
},

clearHistoryState: function pdfHistory_clearHistoryState() {
this._pushOrReplaceState(null, true);
},

_isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
return (state && state.uid >= 0 &&
state.fingerprint && this.fingerprint === state.fingerprint &&
Expand All @@ -143,6 +147,13 @@ var PDFHistory = (function () {

_pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj,
replace) {
//#if CHROME
// history.state.chromecomState is managed by chromecom.js.
if (window.history.state && 'chromecomState' in window.history.state) {
stateObj = stateObj || {};
stateObj.chromecomState = window.history.state.chromecomState;
}
//#endif
if (replace) {
//#if (GENERIC || CHROME)
window.history.replaceState(stateObj, '', document.URL);
Expand Down
4 changes: 2 additions & 2 deletions web/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -795,8 +795,8 @@ var PDFViewerApplication = {
if (!PDFJS.disableHistory && !self.isViewerEmbedded) {
// The browsing history is only enabled when the viewer is standalone,
// i.e. not when it is embedded in a web page.
if (!self.preferenceShowPreviousViewOnLoad && window.history.state) {
window.history.replaceState(null, '');
if (!self.preferenceShowPreviousViewOnLoad) {
PDFHistory.clearHistoryState();
}
self.pdfHistory.initialize(self.documentFingerprint);

Expand Down

0 comments on commit 8aa1c81

Please sign in to comment.