preserve-referer.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. /*
  2. Copyright 2015 Mozilla Foundation
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. /* import-globals-from pdfHandler.js */
  14. /* exported saveReferer */
  15. "use strict";
  16. /**
  17. * This file is one part of the Referer persistency implementation. The other
  18. * part resides in chromecom.js.
  19. *
  20. * This file collects request headers for every http(s) request, and temporarily
  21. * stores the request headers in a dictionary. Upon completion of the request
  22. * (success or failure), the headers are discarded.
  23. * pdfHandler.js will call saveReferer(details) when it is about to redirect to
  24. * the viewer. Upon calling saveReferer, the Referer header is extracted from
  25. * the request headers and saved.
  26. *
  27. * When the viewer is opened, it opens a port ("chromecom-referrer"). This port
  28. * is used to set up the webRequest listeners that stick the Referer headers to
  29. * the HTTP requests created by this extension. When the port is disconnected,
  30. * the webRequest listeners and the referrer information is discarded.
  31. *
  32. * See setReferer in chromecom.js for more explanation of this logic.
  33. */
  34. // Remembers the request headers for every http(s) page request for the duration
  35. // of the request.
  36. var g_requestHeaders = {};
  37. // g_referrers[tabId][frameId] = referrer of PDF frame.
  38. var g_referrers = {};
  39. var extraInfoSpecWithHeaders; // = ['requestHeaders', 'extraHeaders']
  40. (function () {
  41. var requestFilter = {
  42. urls: ["*://*/*"],
  43. types: ["main_frame", "sub_frame"],
  44. };
  45. function registerListener(extraInfoSpec) {
  46. extraInfoSpecWithHeaders = extraInfoSpec;
  47. // May throw if the given extraInfoSpec is unsupported.
  48. chrome.webRequest.onSendHeaders.addListener(
  49. function (details) {
  50. g_requestHeaders[details.requestId] = details.requestHeaders;
  51. },
  52. requestFilter,
  53. extraInfoSpec
  54. );
  55. }
  56. try {
  57. registerListener(["requestHeaders", "extraHeaders"]);
  58. } catch (e) {
  59. // "extraHeaders" is not supported in Chrome 71 and earlier.
  60. registerListener(["requestHeaders"]);
  61. }
  62. chrome.webRequest.onBeforeRedirect.addListener(forgetHeaders, requestFilter);
  63. chrome.webRequest.onCompleted.addListener(forgetHeaders, requestFilter);
  64. chrome.webRequest.onErrorOccurred.addListener(forgetHeaders, requestFilter);
  65. function forgetHeaders(details) {
  66. delete g_requestHeaders[details.requestId];
  67. }
  68. })();
  69. /**
  70. * @param {object} details - onHeadersReceived event data.
  71. */
  72. function saveReferer(details) {
  73. var referer =
  74. g_requestHeaders[details.requestId] &&
  75. getHeaderFromHeaders(g_requestHeaders[details.requestId], "referer");
  76. referer = (referer && referer.value) || "";
  77. if (!g_referrers[details.tabId]) {
  78. g_referrers[details.tabId] = {};
  79. }
  80. g_referrers[details.tabId][details.frameId] = referer;
  81. }
  82. chrome.tabs.onRemoved.addListener(function (tabId) {
  83. delete g_referrers[tabId];
  84. });
  85. // This method binds a webRequest event handler which adds the Referer header
  86. // to matching PDF resource requests (only if the Referer is non-empty). The
  87. // handler is removed as soon as the PDF viewer frame is unloaded.
  88. chrome.runtime.onConnect.addListener(function onReceivePort(port) {
  89. if (port.name !== "chromecom-referrer") {
  90. return;
  91. }
  92. // Note: sender.frameId is only set in Chrome 41+.
  93. if (!("frameId" in port.sender)) {
  94. port.disconnect();
  95. return;
  96. }
  97. var tabId = port.sender.tab.id;
  98. var frameId = port.sender.frameId;
  99. // If the PDF is viewed for the first time, then the referer will be set here.
  100. var referer = (g_referrers[tabId] && g_referrers[tabId][frameId]) || "";
  101. port.onMessage.addListener(function (data) {
  102. // If the viewer was opened directly (without opening a PDF URL first), then
  103. // the background script does not know about g_referrers, but the viewer may
  104. // know about the referer if stored in the history state (see chromecom.js).
  105. if (data.referer) {
  106. referer = data.referer;
  107. }
  108. chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
  109. if (referer) {
  110. // Only add a blocking request handler if the referer has to be rewritten.
  111. chrome.webRequest.onBeforeSendHeaders.addListener(
  112. onBeforeSendHeaders,
  113. {
  114. urls: [data.requestUrl],
  115. types: ["xmlhttprequest"],
  116. tabId,
  117. },
  118. ["blocking", ...extraInfoSpecWithHeaders]
  119. );
  120. }
  121. // Acknowledge the message, and include the latest referer for this frame.
  122. port.postMessage(referer);
  123. });
  124. // The port is only disconnected when the other end reloads.
  125. port.onDisconnect.addListener(function () {
  126. if (g_referrers[tabId]) {
  127. delete g_referrers[tabId][frameId];
  128. }
  129. chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
  130. chrome.webRequest.onHeadersReceived.removeListener(exposeOnHeadersReceived);
  131. });
  132. // Expose some response headers for fetch API calls from PDF.js;
  133. // This is a work-around for https://crbug.com/784528
  134. chrome.webRequest.onHeadersReceived.addListener(
  135. exposeOnHeadersReceived,
  136. {
  137. urls: ["https://*/*"],
  138. types: ["xmlhttprequest"],
  139. tabId,
  140. },
  141. ["blocking", "responseHeaders"]
  142. );
  143. function onBeforeSendHeaders(details) {
  144. if (details.frameId !== frameId) {
  145. return undefined;
  146. }
  147. var headers = details.requestHeaders;
  148. var refererHeader = getHeaderFromHeaders(headers, "referer");
  149. if (!refererHeader) {
  150. refererHeader = { name: "Referer" };
  151. headers.push(refererHeader);
  152. } else if (
  153. refererHeader.value &&
  154. refererHeader.value.lastIndexOf("chrome-extension:", 0) !== 0
  155. ) {
  156. // Sanity check. If the referer is set, and the value is not the URL of
  157. // this extension, then the request was not initiated by this extension.
  158. return undefined;
  159. }
  160. refererHeader.value = referer;
  161. return { requestHeaders: headers };
  162. }
  163. function exposeOnHeadersReceived(details) {
  164. if (details.frameId !== frameId) {
  165. return undefined;
  166. }
  167. var headers = details.responseHeaders;
  168. var aceh = getHeaderFromHeaders(headers, "access-control-expose-headers");
  169. // List of headers that PDF.js uses in src/display/network_utils.js
  170. var acehValue =
  171. "accept-ranges,content-encoding,content-length,content-disposition";
  172. if (aceh) {
  173. aceh.value += "," + acehValue;
  174. } else {
  175. aceh = { name: "Access-Control-Expose-Headers", value: acehValue };
  176. headers.push(aceh);
  177. }
  178. return { responseHeaders: headers };
  179. }
  180. });