network.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. /**
  2. * @licstart The following is the entire license notice for the
  3. * JavaScript code in this page
  4. *
  5. * Copyright 2022 Mozilla Foundation
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. *
  19. * @licend The above is the entire license notice for the
  20. * JavaScript code in this page
  21. */
  22. "use strict";
  23. Object.defineProperty(exports, "__esModule", {
  24. value: true
  25. });
  26. exports.PDFNetworkStream = void 0;
  27. var _util = require("../shared/util.js");
  28. var _network_utils = require("./network_utils.js");
  29. ;
  30. const OK_RESPONSE = 200;
  31. const PARTIAL_CONTENT_RESPONSE = 206;
  32. function getArrayBuffer(xhr) {
  33. const data = xhr.response;
  34. if (typeof data !== "string") {
  35. return data;
  36. }
  37. const array = (0, _util.stringToBytes)(data);
  38. return array.buffer;
  39. }
  40. class NetworkManager {
  41. constructor(url, args = {}) {
  42. this.url = url;
  43. this.isHttp = /^https?:/i.test(url);
  44. this.httpHeaders = this.isHttp && args.httpHeaders || Object.create(null);
  45. this.withCredentials = args.withCredentials || false;
  46. this.getXhr = args.getXhr || function NetworkManager_getXhr() {
  47. return new XMLHttpRequest();
  48. };
  49. this.currXhrId = 0;
  50. this.pendingRequests = Object.create(null);
  51. }
  52. requestRange(begin, end, listeners) {
  53. const args = {
  54. begin,
  55. end
  56. };
  57. for (const prop in listeners) {
  58. args[prop] = listeners[prop];
  59. }
  60. return this.request(args);
  61. }
  62. requestFull(listeners) {
  63. return this.request(listeners);
  64. }
  65. request(args) {
  66. const xhr = this.getXhr();
  67. const xhrId = this.currXhrId++;
  68. const pendingRequest = this.pendingRequests[xhrId] = {
  69. xhr
  70. };
  71. xhr.open("GET", this.url);
  72. xhr.withCredentials = this.withCredentials;
  73. for (const property in this.httpHeaders) {
  74. const value = this.httpHeaders[property];
  75. if (value === undefined) {
  76. continue;
  77. }
  78. xhr.setRequestHeader(property, value);
  79. }
  80. if (this.isHttp && "begin" in args && "end" in args) {
  81. xhr.setRequestHeader("Range", `bytes=${args.begin}-${args.end - 1}`);
  82. pendingRequest.expectedStatus = PARTIAL_CONTENT_RESPONSE;
  83. } else {
  84. pendingRequest.expectedStatus = OK_RESPONSE;
  85. }
  86. xhr.responseType = "arraybuffer";
  87. if (args.onError) {
  88. xhr.onerror = function (evt) {
  89. args.onError(xhr.status);
  90. };
  91. }
  92. xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
  93. xhr.onprogress = this.onProgress.bind(this, xhrId);
  94. pendingRequest.onHeadersReceived = args.onHeadersReceived;
  95. pendingRequest.onDone = args.onDone;
  96. pendingRequest.onError = args.onError;
  97. pendingRequest.onProgress = args.onProgress;
  98. xhr.send(null);
  99. return xhrId;
  100. }
  101. onProgress(xhrId, evt) {
  102. const pendingRequest = this.pendingRequests[xhrId];
  103. if (!pendingRequest) {
  104. return;
  105. }
  106. pendingRequest.onProgress?.(evt);
  107. }
  108. onStateChange(xhrId, evt) {
  109. const pendingRequest = this.pendingRequests[xhrId];
  110. if (!pendingRequest) {
  111. return;
  112. }
  113. const xhr = pendingRequest.xhr;
  114. if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) {
  115. pendingRequest.onHeadersReceived();
  116. delete pendingRequest.onHeadersReceived;
  117. }
  118. if (xhr.readyState !== 4) {
  119. return;
  120. }
  121. if (!(xhrId in this.pendingRequests)) {
  122. return;
  123. }
  124. delete this.pendingRequests[xhrId];
  125. if (xhr.status === 0 && this.isHttp) {
  126. pendingRequest.onError?.(xhr.status);
  127. return;
  128. }
  129. const xhrStatus = xhr.status || OK_RESPONSE;
  130. const ok_response_on_range_request = xhrStatus === OK_RESPONSE && pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE;
  131. if (!ok_response_on_range_request && xhrStatus !== pendingRequest.expectedStatus) {
  132. pendingRequest.onError?.(xhr.status);
  133. return;
  134. }
  135. const chunk = getArrayBuffer(xhr);
  136. if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
  137. const rangeHeader = xhr.getResponseHeader("Content-Range");
  138. const matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
  139. pendingRequest.onDone({
  140. begin: parseInt(matches[1], 10),
  141. chunk
  142. });
  143. } else if (chunk) {
  144. pendingRequest.onDone({
  145. begin: 0,
  146. chunk
  147. });
  148. } else {
  149. pendingRequest.onError?.(xhr.status);
  150. }
  151. }
  152. getRequestXhr(xhrId) {
  153. return this.pendingRequests[xhrId].xhr;
  154. }
  155. isPendingRequest(xhrId) {
  156. return xhrId in this.pendingRequests;
  157. }
  158. abortRequest(xhrId) {
  159. const xhr = this.pendingRequests[xhrId].xhr;
  160. delete this.pendingRequests[xhrId];
  161. xhr.abort();
  162. }
  163. }
  164. class PDFNetworkStream {
  165. constructor(source) {
  166. this._source = source;
  167. this._manager = new NetworkManager(source.url, {
  168. httpHeaders: source.httpHeaders,
  169. withCredentials: source.withCredentials
  170. });
  171. this._rangeChunkSize = source.rangeChunkSize;
  172. this._fullRequestReader = null;
  173. this._rangeRequestReaders = [];
  174. }
  175. _onRangeRequestReaderClosed(reader) {
  176. const i = this._rangeRequestReaders.indexOf(reader);
  177. if (i >= 0) {
  178. this._rangeRequestReaders.splice(i, 1);
  179. }
  180. }
  181. getFullReader() {
  182. (0, _util.assert)(!this._fullRequestReader, "PDFNetworkStream.getFullReader can only be called once.");
  183. this._fullRequestReader = new PDFNetworkStreamFullRequestReader(this._manager, this._source);
  184. return this._fullRequestReader;
  185. }
  186. getRangeReader(begin, end) {
  187. const reader = new PDFNetworkStreamRangeRequestReader(this._manager, begin, end);
  188. reader.onClosed = this._onRangeRequestReaderClosed.bind(this);
  189. this._rangeRequestReaders.push(reader);
  190. return reader;
  191. }
  192. cancelAllRequests(reason) {
  193. this._fullRequestReader?.cancel(reason);
  194. for (const reader of this._rangeRequestReaders.slice(0)) {
  195. reader.cancel(reason);
  196. }
  197. }
  198. }
  199. exports.PDFNetworkStream = PDFNetworkStream;
  200. class PDFNetworkStreamFullRequestReader {
  201. constructor(manager, source) {
  202. this._manager = manager;
  203. const args = {
  204. onHeadersReceived: this._onHeadersReceived.bind(this),
  205. onDone: this._onDone.bind(this),
  206. onError: this._onError.bind(this),
  207. onProgress: this._onProgress.bind(this)
  208. };
  209. this._url = source.url;
  210. this._fullRequestId = manager.requestFull(args);
  211. this._headersReceivedCapability = (0, _util.createPromiseCapability)();
  212. this._disableRange = source.disableRange || false;
  213. this._contentLength = source.length;
  214. this._rangeChunkSize = source.rangeChunkSize;
  215. if (!this._rangeChunkSize && !this._disableRange) {
  216. this._disableRange = true;
  217. }
  218. this._isStreamingSupported = false;
  219. this._isRangeSupported = false;
  220. this._cachedChunks = [];
  221. this._requests = [];
  222. this._done = false;
  223. this._storedError = undefined;
  224. this._filename = null;
  225. this.onProgress = null;
  226. }
  227. _onHeadersReceived() {
  228. const fullRequestXhrId = this._fullRequestId;
  229. const fullRequestXhr = this._manager.getRequestXhr(fullRequestXhrId);
  230. const getResponseHeader = name => {
  231. return fullRequestXhr.getResponseHeader(name);
  232. };
  233. const {
  234. allowRangeRequests,
  235. suggestedLength
  236. } = (0, _network_utils.validateRangeRequestCapabilities)({
  237. getResponseHeader,
  238. isHttp: this._manager.isHttp,
  239. rangeChunkSize: this._rangeChunkSize,
  240. disableRange: this._disableRange
  241. });
  242. if (allowRangeRequests) {
  243. this._isRangeSupported = true;
  244. }
  245. this._contentLength = suggestedLength || this._contentLength;
  246. this._filename = (0, _network_utils.extractFilenameFromHeader)(getResponseHeader);
  247. if (this._isRangeSupported) {
  248. this._manager.abortRequest(fullRequestXhrId);
  249. }
  250. this._headersReceivedCapability.resolve();
  251. }
  252. _onDone(data) {
  253. if (data) {
  254. if (this._requests.length > 0) {
  255. const requestCapability = this._requests.shift();
  256. requestCapability.resolve({
  257. value: data.chunk,
  258. done: false
  259. });
  260. } else {
  261. this._cachedChunks.push(data.chunk);
  262. }
  263. }
  264. this._done = true;
  265. if (this._cachedChunks.length > 0) {
  266. return;
  267. }
  268. for (const requestCapability of this._requests) {
  269. requestCapability.resolve({
  270. value: undefined,
  271. done: true
  272. });
  273. }
  274. this._requests.length = 0;
  275. }
  276. _onError(status) {
  277. this._storedError = (0, _network_utils.createResponseStatusError)(status, this._url);
  278. this._headersReceivedCapability.reject(this._storedError);
  279. for (const requestCapability of this._requests) {
  280. requestCapability.reject(this._storedError);
  281. }
  282. this._requests.length = 0;
  283. this._cachedChunks.length = 0;
  284. }
  285. _onProgress(evt) {
  286. this.onProgress?.({
  287. loaded: evt.loaded,
  288. total: evt.lengthComputable ? evt.total : this._contentLength
  289. });
  290. }
  291. get filename() {
  292. return this._filename;
  293. }
  294. get isRangeSupported() {
  295. return this._isRangeSupported;
  296. }
  297. get isStreamingSupported() {
  298. return this._isStreamingSupported;
  299. }
  300. get contentLength() {
  301. return this._contentLength;
  302. }
  303. get headersReady() {
  304. return this._headersReceivedCapability.promise;
  305. }
  306. async read() {
  307. if (this._storedError) {
  308. throw this._storedError;
  309. }
  310. if (this._cachedChunks.length > 0) {
  311. const chunk = this._cachedChunks.shift();
  312. return {
  313. value: chunk,
  314. done: false
  315. };
  316. }
  317. if (this._done) {
  318. return {
  319. value: undefined,
  320. done: true
  321. };
  322. }
  323. const requestCapability = (0, _util.createPromiseCapability)();
  324. this._requests.push(requestCapability);
  325. return requestCapability.promise;
  326. }
  327. cancel(reason) {
  328. this._done = true;
  329. this._headersReceivedCapability.reject(reason);
  330. for (const requestCapability of this._requests) {
  331. requestCapability.resolve({
  332. value: undefined,
  333. done: true
  334. });
  335. }
  336. this._requests.length = 0;
  337. if (this._manager.isPendingRequest(this._fullRequestId)) {
  338. this._manager.abortRequest(this._fullRequestId);
  339. }
  340. this._fullRequestReader = null;
  341. }
  342. }
  343. class PDFNetworkStreamRangeRequestReader {
  344. constructor(manager, begin, end) {
  345. this._manager = manager;
  346. const args = {
  347. onDone: this._onDone.bind(this),
  348. onError: this._onError.bind(this),
  349. onProgress: this._onProgress.bind(this)
  350. };
  351. this._url = manager.url;
  352. this._requestId = manager.requestRange(begin, end, args);
  353. this._requests = [];
  354. this._queuedChunk = null;
  355. this._done = false;
  356. this._storedError = undefined;
  357. this.onProgress = null;
  358. this.onClosed = null;
  359. }
  360. _close() {
  361. this.onClosed?.(this);
  362. }
  363. _onDone(data) {
  364. const chunk = data.chunk;
  365. if (this._requests.length > 0) {
  366. const requestCapability = this._requests.shift();
  367. requestCapability.resolve({
  368. value: chunk,
  369. done: false
  370. });
  371. } else {
  372. this._queuedChunk = chunk;
  373. }
  374. this._done = true;
  375. for (const requestCapability of this._requests) {
  376. requestCapability.resolve({
  377. value: undefined,
  378. done: true
  379. });
  380. }
  381. this._requests.length = 0;
  382. this._close();
  383. }
  384. _onError(status) {
  385. this._storedError = (0, _network_utils.createResponseStatusError)(status, this._url);
  386. for (const requestCapability of this._requests) {
  387. requestCapability.reject(this._storedError);
  388. }
  389. this._requests.length = 0;
  390. this._queuedChunk = null;
  391. }
  392. _onProgress(evt) {
  393. if (!this.isStreamingSupported) {
  394. this.onProgress?.({
  395. loaded: evt.loaded
  396. });
  397. }
  398. }
  399. get isStreamingSupported() {
  400. return false;
  401. }
  402. async read() {
  403. if (this._storedError) {
  404. throw this._storedError;
  405. }
  406. if (this._queuedChunk !== null) {
  407. const chunk = this._queuedChunk;
  408. this._queuedChunk = null;
  409. return {
  410. value: chunk,
  411. done: false
  412. };
  413. }
  414. if (this._done) {
  415. return {
  416. value: undefined,
  417. done: true
  418. };
  419. }
  420. const requestCapability = (0, _util.createPromiseCapability)();
  421. this._requests.push(requestCapability);
  422. return requestCapability.promise;
  423. }
  424. cancel(reason) {
  425. this._done = true;
  426. for (const requestCapability of this._requests) {
  427. requestCapability.resolve({
  428. value: undefined,
  429. done: true
  430. });
  431. }
  432. this._requests.length = 0;
  433. if (this._manager.isPendingRequest(this._requestId)) {
  434. this._manager.abortRequest(this._requestId);
  435. }
  436. this._close();
  437. }
  438. }