UNPKG

13.1 kBJavaScriptView Raw
1/**
2 * @licstart The following is the entire license notice for the
3 * Javascript code in this page
4 *
5 * Copyright 2021 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
24Object.defineProperty(exports, "__esModule", {
25 value: true
26});
27exports.PDFNetworkStream = void 0;
28
29var _util = require("../shared/util.js");
30
31var _network_utils = require("./network_utils.js");
32
33;
34const OK_RESPONSE = 200;
35const PARTIAL_CONTENT_RESPONSE = 206;
36
37function getArrayBuffer(xhr) {
38 const data = xhr.response;
39
40 if (typeof data !== "string") {
41 return data;
42 }
43
44 const array = (0, _util.stringToBytes)(data);
45 return array.buffer;
46}
47
48class NetworkManager {
49 constructor(url, args = {}) {
50 this.url = url;
51 this.isHttp = /^https?:/i.test(url);
52 this.httpHeaders = this.isHttp && args.httpHeaders || Object.create(null);
53 this.withCredentials = args.withCredentials || false;
54
55 this.getXhr = args.getXhr || function NetworkManager_getXhr() {
56 return new XMLHttpRequest();
57 };
58
59 this.currXhrId = 0;
60 this.pendingRequests = Object.create(null);
61 }
62
63 requestRange(begin, end, listeners) {
64 const args = {
65 begin,
66 end
67 };
68
69 for (const prop in listeners) {
70 args[prop] = listeners[prop];
71 }
72
73 return this.request(args);
74 }
75
76 requestFull(listeners) {
77 return this.request(listeners);
78 }
79
80 request(args) {
81 const xhr = this.getXhr();
82 const xhrId = this.currXhrId++;
83 const pendingRequest = this.pendingRequests[xhrId] = {
84 xhr
85 };
86 xhr.open("GET", this.url);
87 xhr.withCredentials = this.withCredentials;
88
89 for (const property in this.httpHeaders) {
90 const value = this.httpHeaders[property];
91
92 if (typeof value === "undefined") {
93 continue;
94 }
95
96 xhr.setRequestHeader(property, value);
97 }
98
99 if (this.isHttp && "begin" in args && "end" in args) {
100 xhr.setRequestHeader("Range", `bytes=${args.begin}-${args.end - 1}`);
101 pendingRequest.expectedStatus = PARTIAL_CONTENT_RESPONSE;
102 } else {
103 pendingRequest.expectedStatus = OK_RESPONSE;
104 }
105
106 xhr.responseType = "arraybuffer";
107
108 if (args.onError) {
109 xhr.onerror = function (evt) {
110 args.onError(xhr.status);
111 };
112 }
113
114 xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
115 xhr.onprogress = this.onProgress.bind(this, xhrId);
116 pendingRequest.onHeadersReceived = args.onHeadersReceived;
117 pendingRequest.onDone = args.onDone;
118 pendingRequest.onError = args.onError;
119 pendingRequest.onProgress = args.onProgress;
120 xhr.send(null);
121 return xhrId;
122 }
123
124 onProgress(xhrId, evt) {
125 const pendingRequest = this.pendingRequests[xhrId];
126
127 if (!pendingRequest) {
128 return;
129 }
130
131 pendingRequest.onProgress?.(evt);
132 }
133
134 onStateChange(xhrId, evt) {
135 const pendingRequest = this.pendingRequests[xhrId];
136
137 if (!pendingRequest) {
138 return;
139 }
140
141 const xhr = pendingRequest.xhr;
142
143 if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) {
144 pendingRequest.onHeadersReceived();
145 delete pendingRequest.onHeadersReceived;
146 }
147
148 if (xhr.readyState !== 4) {
149 return;
150 }
151
152 if (!(xhrId in this.pendingRequests)) {
153 return;
154 }
155
156 delete this.pendingRequests[xhrId];
157
158 if (xhr.status === 0 && this.isHttp) {
159 pendingRequest.onError?.(xhr.status);
160 return;
161 }
162
163 const xhrStatus = xhr.status || OK_RESPONSE;
164 const ok_response_on_range_request = xhrStatus === OK_RESPONSE && pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE;
165
166 if (!ok_response_on_range_request && xhrStatus !== pendingRequest.expectedStatus) {
167 pendingRequest.onError?.(xhr.status);
168 return;
169 }
170
171 const chunk = getArrayBuffer(xhr);
172
173 if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
174 const rangeHeader = xhr.getResponseHeader("Content-Range");
175 const matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
176 pendingRequest.onDone({
177 begin: parseInt(matches[1], 10),
178 chunk
179 });
180 } else if (chunk) {
181 pendingRequest.onDone({
182 begin: 0,
183 chunk
184 });
185 } else {
186 pendingRequest.onError?.(xhr.status);
187 }
188 }
189
190 getRequestXhr(xhrId) {
191 return this.pendingRequests[xhrId].xhr;
192 }
193
194 isPendingRequest(xhrId) {
195 return xhrId in this.pendingRequests;
196 }
197
198 abortRequest(xhrId) {
199 const xhr = this.pendingRequests[xhrId].xhr;
200 delete this.pendingRequests[xhrId];
201 xhr.abort();
202 }
203
204}
205
206class PDFNetworkStream {
207 constructor(source) {
208 this._source = source;
209 this._manager = new NetworkManager(source.url, {
210 httpHeaders: source.httpHeaders,
211 withCredentials: source.withCredentials
212 });
213 this._rangeChunkSize = source.rangeChunkSize;
214 this._fullRequestReader = null;
215 this._rangeRequestReaders = [];
216 }
217
218 _onRangeRequestReaderClosed(reader) {
219 const i = this._rangeRequestReaders.indexOf(reader);
220
221 if (i >= 0) {
222 this._rangeRequestReaders.splice(i, 1);
223 }
224 }
225
226 getFullReader() {
227 (0, _util.assert)(!this._fullRequestReader, "PDFNetworkStream.getFullReader can only be called once.");
228 this._fullRequestReader = new PDFNetworkStreamFullRequestReader(this._manager, this._source);
229 return this._fullRequestReader;
230 }
231
232 getRangeReader(begin, end) {
233 const reader = new PDFNetworkStreamRangeRequestReader(this._manager, begin, end);
234 reader.onClosed = this._onRangeRequestReaderClosed.bind(this);
235
236 this._rangeRequestReaders.push(reader);
237
238 return reader;
239 }
240
241 cancelAllRequests(reason) {
242 this._fullRequestReader?.cancel(reason);
243
244 for (const reader of this._rangeRequestReaders.slice(0)) {
245 reader.cancel(reason);
246 }
247 }
248
249}
250
251exports.PDFNetworkStream = PDFNetworkStream;
252
253class PDFNetworkStreamFullRequestReader {
254 constructor(manager, source) {
255 this._manager = manager;
256 const args = {
257 onHeadersReceived: this._onHeadersReceived.bind(this),
258 onDone: this._onDone.bind(this),
259 onError: this._onError.bind(this),
260 onProgress: this._onProgress.bind(this)
261 };
262 this._url = source.url;
263 this._fullRequestId = manager.requestFull(args);
264 this._headersReceivedCapability = (0, _util.createPromiseCapability)();
265 this._disableRange = source.disableRange || false;
266 this._contentLength = source.length;
267 this._rangeChunkSize = source.rangeChunkSize;
268
269 if (!this._rangeChunkSize && !this._disableRange) {
270 this._disableRange = true;
271 }
272
273 this._isStreamingSupported = false;
274 this._isRangeSupported = false;
275 this._cachedChunks = [];
276 this._requests = [];
277 this._done = false;
278 this._storedError = undefined;
279 this._filename = null;
280 this.onProgress = null;
281 }
282
283 _onHeadersReceived() {
284 const fullRequestXhrId = this._fullRequestId;
285
286 const fullRequestXhr = this._manager.getRequestXhr(fullRequestXhrId);
287
288 const getResponseHeader = name => {
289 return fullRequestXhr.getResponseHeader(name);
290 };
291
292 const {
293 allowRangeRequests,
294 suggestedLength
295 } = (0, _network_utils.validateRangeRequestCapabilities)({
296 getResponseHeader,
297 isHttp: this._manager.isHttp,
298 rangeChunkSize: this._rangeChunkSize,
299 disableRange: this._disableRange
300 });
301
302 if (allowRangeRequests) {
303 this._isRangeSupported = true;
304 }
305
306 this._contentLength = suggestedLength || this._contentLength;
307 this._filename = (0, _network_utils.extractFilenameFromHeader)(getResponseHeader);
308
309 if (this._isRangeSupported) {
310 this._manager.abortRequest(fullRequestXhrId);
311 }
312
313 this._headersReceivedCapability.resolve();
314 }
315
316 _onDone(data) {
317 if (data) {
318 if (this._requests.length > 0) {
319 const requestCapability = this._requests.shift();
320
321 requestCapability.resolve({
322 value: data.chunk,
323 done: false
324 });
325 } else {
326 this._cachedChunks.push(data.chunk);
327 }
328 }
329
330 this._done = true;
331
332 if (this._cachedChunks.length > 0) {
333 return;
334 }
335
336 for (const requestCapability of this._requests) {
337 requestCapability.resolve({
338 value: undefined,
339 done: true
340 });
341 }
342
343 this._requests.length = 0;
344 }
345
346 _onError(status) {
347 this._storedError = (0, _network_utils.createResponseStatusError)(status, this._url);
348
349 this._headersReceivedCapability.reject(this._storedError);
350
351 for (const requestCapability of this._requests) {
352 requestCapability.reject(this._storedError);
353 }
354
355 this._requests.length = 0;
356 this._cachedChunks.length = 0;
357 }
358
359 _onProgress(evt) {
360 this.onProgress?.({
361 loaded: evt.loaded,
362 total: evt.lengthComputable ? evt.total : this._contentLength
363 });
364 }
365
366 get filename() {
367 return this._filename;
368 }
369
370 get isRangeSupported() {
371 return this._isRangeSupported;
372 }
373
374 get isStreamingSupported() {
375 return this._isStreamingSupported;
376 }
377
378 get contentLength() {
379 return this._contentLength;
380 }
381
382 get headersReady() {
383 return this._headersReceivedCapability.promise;
384 }
385
386 async read() {
387 if (this._storedError) {
388 throw this._storedError;
389 }
390
391 if (this._cachedChunks.length > 0) {
392 const chunk = this._cachedChunks.shift();
393
394 return {
395 value: chunk,
396 done: false
397 };
398 }
399
400 if (this._done) {
401 return {
402 value: undefined,
403 done: true
404 };
405 }
406
407 const requestCapability = (0, _util.createPromiseCapability)();
408
409 this._requests.push(requestCapability);
410
411 return requestCapability.promise;
412 }
413
414 cancel(reason) {
415 this._done = true;
416
417 this._headersReceivedCapability.reject(reason);
418
419 for (const requestCapability of this._requests) {
420 requestCapability.resolve({
421 value: undefined,
422 done: true
423 });
424 }
425
426 this._requests.length = 0;
427
428 if (this._manager.isPendingRequest(this._fullRequestId)) {
429 this._manager.abortRequest(this._fullRequestId);
430 }
431
432 this._fullRequestReader = null;
433 }
434
435}
436
437class PDFNetworkStreamRangeRequestReader {
438 constructor(manager, begin, end) {
439 this._manager = manager;
440 const args = {
441 onDone: this._onDone.bind(this),
442 onError: this._onError.bind(this),
443 onProgress: this._onProgress.bind(this)
444 };
445 this._url = manager.url;
446 this._requestId = manager.requestRange(begin, end, args);
447 this._requests = [];
448 this._queuedChunk = null;
449 this._done = false;
450 this._storedError = undefined;
451 this.onProgress = null;
452 this.onClosed = null;
453 }
454
455 _close() {
456 this.onClosed?.(this);
457 }
458
459 _onDone(data) {
460 const chunk = data.chunk;
461
462 if (this._requests.length > 0) {
463 const requestCapability = this._requests.shift();
464
465 requestCapability.resolve({
466 value: chunk,
467 done: false
468 });
469 } else {
470 this._queuedChunk = chunk;
471 }
472
473 this._done = true;
474
475 for (const requestCapability of this._requests) {
476 requestCapability.resolve({
477 value: undefined,
478 done: true
479 });
480 }
481
482 this._requests.length = 0;
483
484 this._close();
485 }
486
487 _onError(status) {
488 this._storedError = (0, _network_utils.createResponseStatusError)(status, this._url);
489
490 for (const requestCapability of this._requests) {
491 requestCapability.reject(this._storedError);
492 }
493
494 this._requests.length = 0;
495 this._queuedChunk = null;
496 }
497
498 _onProgress(evt) {
499 if (!this.isStreamingSupported) {
500 this.onProgress?.({
501 loaded: evt.loaded
502 });
503 }
504 }
505
506 get isStreamingSupported() {
507 return false;
508 }
509
510 async read() {
511 if (this._storedError) {
512 throw this._storedError;
513 }
514
515 if (this._queuedChunk !== null) {
516 const chunk = this._queuedChunk;
517 this._queuedChunk = null;
518 return {
519 value: chunk,
520 done: false
521 };
522 }
523
524 if (this._done) {
525 return {
526 value: undefined,
527 done: true
528 };
529 }
530
531 const requestCapability = (0, _util.createPromiseCapability)();
532
533 this._requests.push(requestCapability);
534
535 return requestCapability.promise;
536 }
537
538 cancel(reason) {
539 this._done = true;
540
541 for (const requestCapability of this._requests) {
542 requestCapability.resolve({
543 value: undefined,
544 done: true
545 });
546 }
547
548 this._requests.length = 0;
549
550 if (this._manager.isPendingRequest(this._requestId)) {
551 this._manager.abortRequest(this._requestId);
552 }
553
554 this._close();
555 }
556
557}
\No newline at end of file