| /** |
| * @licstart The following is the entire license notice for the |
| * Javascript code in this page |
| * |
| * Copyright 2020 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. |
| * |
| * @licend The above is the entire license notice for the |
| * Javascript code in this page |
| */ |
| "use strict"; |
| |
| Object.defineProperty(exports, "__esModule", { |
| value: true |
| }); |
| exports.PDFNetworkStream = void 0; |
| |
| var _util = require("../shared/util.js"); |
| |
| var _network_utils = require("./network_utils.js"); |
| |
| ; |
| const OK_RESPONSE = 200; |
| const PARTIAL_CONTENT_RESPONSE = 206; |
| |
| function getArrayBuffer(xhr) { |
| const data = xhr.response; |
| |
| if (typeof data !== "string") { |
| return data; |
| } |
| |
| const array = (0, _util.stringToBytes)(data); |
| return array.buffer; |
| } |
| |
| class NetworkManager { |
| constructor(url, args) { |
| this.url = url; |
| args = args || {}; |
| this.isHttp = /^https?:/i.test(url); |
| this.httpHeaders = this.isHttp && args.httpHeaders || {}; |
| this.withCredentials = args.withCredentials || false; |
| |
| this.getXhr = args.getXhr || function NetworkManager_getXhr() { |
| return new XMLHttpRequest(); |
| }; |
| |
| this.currXhrId = 0; |
| this.pendingRequests = Object.create(null); |
| } |
| |
| requestRange(begin, end, listeners) { |
| const args = { |
| begin, |
| end |
| }; |
| |
| for (const prop in listeners) { |
| args[prop] = listeners[prop]; |
| } |
| |
| return this.request(args); |
| } |
| |
| requestFull(listeners) { |
| return this.request(listeners); |
| } |
| |
| request(args) { |
| const xhr = this.getXhr(); |
| const xhrId = this.currXhrId++; |
| const pendingRequest = this.pendingRequests[xhrId] = { |
| xhr |
| }; |
| xhr.open("GET", this.url); |
| xhr.withCredentials = this.withCredentials; |
| |
| for (const property in this.httpHeaders) { |
| const value = this.httpHeaders[property]; |
| |
| if (typeof value === "undefined") { |
| continue; |
| } |
| |
| xhr.setRequestHeader(property, value); |
| } |
| |
| if (this.isHttp && "begin" in args && "end" in args) { |
| xhr.setRequestHeader("Range", `bytes=${args.begin}-${args.end - 1}`); |
| pendingRequest.expectedStatus = PARTIAL_CONTENT_RESPONSE; |
| } else { |
| pendingRequest.expectedStatus = OK_RESPONSE; |
| } |
| |
| xhr.responseType = "arraybuffer"; |
| |
| if (args.onError) { |
| xhr.onerror = function (evt) { |
| args.onError(xhr.status); |
| }; |
| } |
| |
| xhr.onreadystatechange = this.onStateChange.bind(this, xhrId); |
| xhr.onprogress = this.onProgress.bind(this, xhrId); |
| pendingRequest.onHeadersReceived = args.onHeadersReceived; |
| pendingRequest.onDone = args.onDone; |
| pendingRequest.onError = args.onError; |
| pendingRequest.onProgress = args.onProgress; |
| xhr.send(null); |
| return xhrId; |
| } |
| |
| onProgress(xhrId, evt) { |
| const pendingRequest = this.pendingRequests[xhrId]; |
| |
| if (!pendingRequest) { |
| return; |
| } |
| |
| if (pendingRequest.onProgress) { |
| pendingRequest.onProgress(evt); |
| } |
| } |
| |
| onStateChange(xhrId, evt) { |
| const pendingRequest = this.pendingRequests[xhrId]; |
| |
| if (!pendingRequest) { |
| return; |
| } |
| |
| const xhr = pendingRequest.xhr; |
| |
| if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) { |
| pendingRequest.onHeadersReceived(); |
| delete pendingRequest.onHeadersReceived; |
| } |
| |
| if (xhr.readyState !== 4) { |
| return; |
| } |
| |
| if (!(xhrId in this.pendingRequests)) { |
| return; |
| } |
| |
| delete this.pendingRequests[xhrId]; |
| |
| if (xhr.status === 0 && this.isHttp) { |
| if (pendingRequest.onError) { |
| pendingRequest.onError(xhr.status); |
| } |
| |
| return; |
| } |
| |
| const xhrStatus = xhr.status || OK_RESPONSE; |
| const ok_response_on_range_request = xhrStatus === OK_RESPONSE && pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE; |
| |
| if (!ok_response_on_range_request && xhrStatus !== pendingRequest.expectedStatus) { |
| if (pendingRequest.onError) { |
| pendingRequest.onError(xhr.status); |
| } |
| |
| return; |
| } |
| |
| const chunk = getArrayBuffer(xhr); |
| |
| if (xhrStatus === PARTIAL_CONTENT_RESPONSE) { |
| const rangeHeader = xhr.getResponseHeader("Content-Range"); |
| const matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader); |
| pendingRequest.onDone({ |
| begin: parseInt(matches[1], 10), |
| chunk |
| }); |
| } else if (chunk) { |
| pendingRequest.onDone({ |
| begin: 0, |
| chunk |
| }); |
| } else if (pendingRequest.onError) { |
| pendingRequest.onError(xhr.status); |
| } |
| } |
| |
| getRequestXhr(xhrId) { |
| return this.pendingRequests[xhrId].xhr; |
| } |
| |
| isPendingRequest(xhrId) { |
| return xhrId in this.pendingRequests; |
| } |
| |
| abortRequest(xhrId) { |
| const xhr = this.pendingRequests[xhrId].xhr; |
| delete this.pendingRequests[xhrId]; |
| xhr.abort(); |
| } |
| |
| } |
| |
| class PDFNetworkStream { |
| constructor(source) { |
| this._source = source; |
| this._manager = new NetworkManager(source.url, { |
| httpHeaders: source.httpHeaders, |
| withCredentials: source.withCredentials |
| }); |
| this._rangeChunkSize = source.rangeChunkSize; |
| this._fullRequestReader = null; |
| this._rangeRequestReaders = []; |
| } |
| |
| _onRangeRequestReaderClosed(reader) { |
| const i = this._rangeRequestReaders.indexOf(reader); |
| |
| if (i >= 0) { |
| this._rangeRequestReaders.splice(i, 1); |
| } |
| } |
| |
| getFullReader() { |
| (0, _util.assert)(!this._fullRequestReader, "PDFNetworkStream.getFullReader can only be called once."); |
| this._fullRequestReader = new PDFNetworkStreamFullRequestReader(this._manager, this._source); |
| return this._fullRequestReader; |
| } |
| |
| getRangeReader(begin, end) { |
| const reader = new PDFNetworkStreamRangeRequestReader(this._manager, begin, end); |
| reader.onClosed = this._onRangeRequestReaderClosed.bind(this); |
| |
| this._rangeRequestReaders.push(reader); |
| |
| return reader; |
| } |
| |
| cancelAllRequests(reason) { |
| if (this._fullRequestReader) { |
| this._fullRequestReader.cancel(reason); |
| } |
| |
| const readers = this._rangeRequestReaders.slice(0); |
| |
| readers.forEach(function (reader) { |
| reader.cancel(reason); |
| }); |
| } |
| |
| } |
| |
| exports.PDFNetworkStream = PDFNetworkStream; |
| |
| class PDFNetworkStreamFullRequestReader { |
| constructor(manager, source) { |
| this._manager = manager; |
| const args = { |
| onHeadersReceived: this._onHeadersReceived.bind(this), |
| onDone: this._onDone.bind(this), |
| onError: this._onError.bind(this), |
| onProgress: this._onProgress.bind(this) |
| }; |
| this._url = source.url; |
| this._fullRequestId = manager.requestFull(args); |
| this._headersReceivedCapability = (0, _util.createPromiseCapability)(); |
| this._disableRange = source.disableRange || false; |
| this._contentLength = source.length; |
| this._rangeChunkSize = source.rangeChunkSize; |
| |
| if (!this._rangeChunkSize && !this._disableRange) { |
| this._disableRange = true; |
| } |
| |
| this._isStreamingSupported = false; |
| this._isRangeSupported = false; |
| this._cachedChunks = []; |
| this._requests = []; |
| this._done = false; |
| this._storedError = undefined; |
| this._filename = null; |
| this.onProgress = null; |
| } |
| |
| _onHeadersReceived() { |
| const fullRequestXhrId = this._fullRequestId; |
| |
| const fullRequestXhr = this._manager.getRequestXhr(fullRequestXhrId); |
| |
| const getResponseHeader = name => { |
| return fullRequestXhr.getResponseHeader(name); |
| }; |
| |
| const { |
| allowRangeRequests, |
| suggestedLength |
| } = (0, _network_utils.validateRangeRequestCapabilities)({ |
| getResponseHeader, |
| isHttp: this._manager.isHttp, |
| rangeChunkSize: this._rangeChunkSize, |
| disableRange: this._disableRange |
| }); |
| |
| if (allowRangeRequests) { |
| this._isRangeSupported = true; |
| } |
| |
| this._contentLength = suggestedLength || this._contentLength; |
| this._filename = (0, _network_utils.extractFilenameFromHeader)(getResponseHeader); |
| |
| if (this._isRangeSupported) { |
| this._manager.abortRequest(fullRequestXhrId); |
| } |
| |
| this._headersReceivedCapability.resolve(); |
| } |
| |
| _onDone(args) { |
| if (args) { |
| if (this._requests.length > 0) { |
| const requestCapability = this._requests.shift(); |
| |
| requestCapability.resolve({ |
| value: args.chunk, |
| done: false |
| }); |
| } else { |
| this._cachedChunks.push(args.chunk); |
| } |
| } |
| |
| this._done = true; |
| |
| if (this._cachedChunks.length > 0) { |
| return; |
| } |
| |
| this._requests.forEach(function (requestCapability) { |
| requestCapability.resolve({ |
| value: undefined, |
| done: true |
| }); |
| }); |
| |
| this._requests = []; |
| } |
| |
| _onError(status) { |
| const url = this._url; |
| const exception = (0, _network_utils.createResponseStatusError)(status, url); |
| this._storedError = exception; |
| |
| this._headersReceivedCapability.reject(exception); |
| |
| this._requests.forEach(function (requestCapability) { |
| requestCapability.reject(exception); |
| }); |
| |
| this._requests = []; |
| this._cachedChunks = []; |
| } |
| |
| _onProgress(data) { |
| if (this.onProgress) { |
| this.onProgress({ |
| loaded: data.loaded, |
| total: data.lengthComputable ? data.total : this._contentLength |
| }); |
| } |
| } |
| |
| get filename() { |
| return this._filename; |
| } |
| |
| get isRangeSupported() { |
| return this._isRangeSupported; |
| } |
| |
| get isStreamingSupported() { |
| return this._isStreamingSupported; |
| } |
| |
| get contentLength() { |
| return this._contentLength; |
| } |
| |
| get headersReady() { |
| return this._headersReceivedCapability.promise; |
| } |
| |
| async read() { |
| if (this._storedError) { |
| throw this._storedError; |
| } |
| |
| if (this._cachedChunks.length > 0) { |
| const chunk = this._cachedChunks.shift(); |
| |
| return { |
| value: chunk, |
| done: false |
| }; |
| } |
| |
| if (this._done) { |
| return { |
| value: undefined, |
| done: true |
| }; |
| } |
| |
| const requestCapability = (0, _util.createPromiseCapability)(); |
| |
| this._requests.push(requestCapability); |
| |
| return requestCapability.promise; |
| } |
| |
| cancel(reason) { |
| this._done = true; |
| |
| this._headersReceivedCapability.reject(reason); |
| |
| this._requests.forEach(function (requestCapability) { |
| requestCapability.resolve({ |
| value: undefined, |
| done: true |
| }); |
| }); |
| |
| this._requests = []; |
| |
| if (this._manager.isPendingRequest(this._fullRequestId)) { |
| this._manager.abortRequest(this._fullRequestId); |
| } |
| |
| this._fullRequestReader = null; |
| } |
| |
| } |
| |
| class PDFNetworkStreamRangeRequestReader { |
| constructor(manager, begin, end) { |
| this._manager = manager; |
| const args = { |
| onDone: this._onDone.bind(this), |
| onProgress: this._onProgress.bind(this) |
| }; |
| this._requestId = manager.requestRange(begin, end, args); |
| this._requests = []; |
| this._queuedChunk = null; |
| this._done = false; |
| this.onProgress = null; |
| this.onClosed = null; |
| } |
| |
| _close() { |
| if (this.onClosed) { |
| this.onClosed(this); |
| } |
| } |
| |
| _onDone(data) { |
| const chunk = data.chunk; |
| |
| if (this._requests.length > 0) { |
| const requestCapability = this._requests.shift(); |
| |
| requestCapability.resolve({ |
| value: chunk, |
| done: false |
| }); |
| } else { |
| this._queuedChunk = chunk; |
| } |
| |
| this._done = true; |
| |
| this._requests.forEach(function (requestCapability) { |
| requestCapability.resolve({ |
| value: undefined, |
| done: true |
| }); |
| }); |
| |
| this._requests = []; |
| |
| this._close(); |
| } |
| |
| _onProgress(evt) { |
| if (!this.isStreamingSupported && this.onProgress) { |
| this.onProgress({ |
| loaded: evt.loaded |
| }); |
| } |
| } |
| |
| get isStreamingSupported() { |
| return false; |
| } |
| |
| async read() { |
| if (this._queuedChunk !== null) { |
| const chunk = this._queuedChunk; |
| this._queuedChunk = null; |
| return { |
| value: chunk, |
| done: false |
| }; |
| } |
| |
| if (this._done) { |
| return { |
| value: undefined, |
| done: true |
| }; |
| } |
| |
| const requestCapability = (0, _util.createPromiseCapability)(); |
| |
| this._requests.push(requestCapability); |
| |
| return requestCapability.promise; |
| } |
| |
| cancel(reason) { |
| this._done = true; |
| |
| this._requests.forEach(function (requestCapability) { |
| requestCapability.resolve({ |
| value: undefined, |
| done: true |
| }); |
| }); |
| |
| this._requests = []; |
| |
| if (this._manager.isPendingRequest(this._requestId)) { |
| this._manager.abortRequest(this._requestId); |
| } |
| |
| this._close(); |
| } |
| |
| } |