text: /**
   * @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.PDFNodeStream = void 0;
  
  var _util = require('../shared/util.js');
  
  var _network_utils = require('./network_utils.js');
  
  const fs = require('fs');
  
  const http = require('http');
  
  const https = require('https');
  
  const url = require('url');
  
  const fileUriRegex = /^file:\/\/\/[a-zA-Z]:\//;
  
  function parseUrl(sourceUrl) {
    const parsedUrl = url.parse(sourceUrl);
  
    if (parsedUrl.protocol === 'file:' || parsedUrl.host) {
      return parsedUrl;
    }
  
    if (/^[a-z]:[/\\]/i.test(sourceUrl)) {
      return url.parse(`file:///${sourceUrl}`);
    }
  
    if (!parsedUrl.host) {
      parsedUrl.protocol = 'file:';
    }
  
    return parsedUrl;
  }
  
  class PDFNodeStream {
    constructor(source) {
      this.source = source;
      this.url = parseUrl(source.url);
      this.isHttp = this.url.protocol === 'http:' || this.url.protocol === 'https:';
      this.isFsUrl = this.url.protocol === 'file:';
      this.httpHeaders = (this.isHttp && source.httpHeaders) || {};
      this._fullRequestReader = null;
      this._rangeRequestReaders = [];
    }
  
    get _progressiveDataLength() {
      return this._fullRequestReader ? this._fullRequestReader._loaded : 0;
    }
  
    getFullReader() {
      (0, _util.assert)(
        !this._fullRequestReader,
        'PDFNodeStream.getFullReader can only be called once.'
      );
      this._fullRequestReader = this.isFsUrl
        ? new PDFNodeStreamFsFullReader(this)
        : new PDFNodeStreamFullReader(this);
      return this._fullRequestReader;
    }
  
    getRangeReader(start, end) {
      if (end <= this._progressiveDataLength) {
        return null;
      }
  
      const rangeReader = this.isFsUrl
        ? new PDFNodeStreamFsRangeReader(this, start, end)
        : new PDFNodeStreamRangeReader(this, start, end);
  
      this._rangeRequestReaders.push(rangeReader);
  
      return rangeReader;
    }
  
    cancelAllRequests(reason) {
      if (this._fullRequestReader) {
        this._fullRequestReader.cancel(reason);
      }
  
      const readers = this._rangeRequestReaders.slice(0);
  
      readers.forEach(function(reader) {
        reader.cancel(reason);
      });
    }
  }
  
  exports.PDFNodeStream = PDFNodeStream;
  
  class BaseFullReader {
    constructor(stream) {
      this._url = stream.url;
      this._done = false;
      this._storedError = null;
      this.onProgress = null;
      const source = stream.source;
      this._contentLength = source.length;
      this._loaded = 0;
      this._filename = null;
      this._disableRange = source.disableRange || false;
      this._rangeChunkSize = source.rangeChunkSize;
  
      if (!this._rangeChunkSize && !this._disableRange) {
        this._disableRange = true;
      }
  
      this._isStreamingSupported = !source.disableStream;
      this._isRangeSupported = !source.disableRange;
      this._readableStream = null;
      this._readCapability = (0, _util.createPromiseCapability)();
      this._headersCapability = (0, _util.createPromiseCapability)();
    }
  
    get headersReady() {
      return this._headersCapability.promise;
    }
  
    get filename() {
      return this._filename;
    }
  
    get contentLength() {
      return this._contentLength;
    }
  
    get isRangeSupported() {
      return this._isRangeSupported;
    }
  
    get isStreamingSupported() {
      return this._isStreamingSupported;
    }
  
    async read() {
      await this._readCapability.promise;
  
      if (this._done) {
        return {
          value: undefined,
          done: true
        };
      }
  
      if (this._storedError) {
        throw this._storedError;
      }
  
      const chunk = this._readableStream.read();
  
      if (chunk === null) {
        this._readCapability = (0, _util.createPromiseCapability)();
        return this.read();
      }
  
      this._loaded += chunk.length;
  
      if (this.onProgress) {
        this.onProgress({
          loaded: this._loaded,
          total: this._contentLength
        });
      }
  
      const buffer = new Uint8Array(chunk).buffer;
      return {
        value: buffer,
        done: false
      };
    }
  
    cancel(reason) {
      if (!this._readableStream) {
        this._error(reason);
  
        return;
      }
  
      this._readableStream.destroy(reason);
    }
  
    _error(reason) {
      this._storedError = reason;
  
      this._readCapability.resolve();
    }
  
    _setReadableStream(readableStream) {
      this._readableStream = readableStream;
      readableStream.on('readable', () => {
        this._readCapability.resolve();
      });
      readableStream.on('end', () => {
        readableStream.destroy();
        this._done = true;
  
        this._readCapability.resolve();
      });
      readableStream.on('error', reason => {
        this._error(reason);
      });
  
      if (!this._isStreamingSupported && this._isRangeSupported) {
        this._error(new _util.AbortException('streaming is disabled'));
      }
  
      if (this._storedError) {
        this._readableStream.destroy(this._storedError);
      }
    }
  }
  
  class BaseRangeReader {
    constructor(stream) {
      this._url = stream.url;
      this._done = false;
      this._storedError = null;
      this.onProgress = null;
      this._loaded = 0;
      this._readableStream = null;
      this._readCapability = (0, _util.createPromiseCapability)();
      const source = stream.source;
      this._isStreamingSupported = !source.disableStream;
    }
  
    get isStreamingSupported() {
      return this._isStreamingSupported;
    }
  
    async read() {
      await this._readCapability.promise;
  
      if (this._done) {
        return {
          value: undefined,
          done: true
        };
      }
  
      if (this._storedError) {
        throw this._storedError;
      }
  
      const chunk = this._readableStream.read();
  
      if (chunk === null) {
        this._readCapability = (0, _util.createPromiseCapability)();
        return this.read();
      }
  
      this._loaded += chunk.length;
  
      if (this.onProgress) {
        this.onProgress({
          loaded: this._loaded
        });
      }
  
      const buffer = new Uint8Array(chunk).buffer;
      return {
        value: buffer,
        done: false
      };
    }
  
    cancel(reason) {
      if (!this._readableStream) {
        this._error(reason);
  
        return;
      }
  
      this._readableStream.destroy(reason);
    }
  
    _error(reason) {
      this._storedError = reason;
  
      this._readCapability.resolve();
    }
  
    _setReadableStream(readableStream) {
      this._readableStream = readableStream;
      readableStream.on('readable', () => {
        this._readCapability.resolve();
      });
      readableStream.on('end', () => {
        readableStream.destroy();
        this._done = true;
  
        this._readCapability.resolve();
      });
      readableStream.on('error', reason => {
        this._error(reason);
      });
  
      if (this._storedError) {
        this._readableStream.destroy(this._storedError);
      }
    }
  }
  
  function createRequestOptions(parsedUrl, headers) {
    return {
      protocol: parsedUrl.protocol,
      auth: parsedUrl.auth,
      host: parsedUrl.hostname,
      port: parsedUrl.port,
      path: parsedUrl.path,
      method: 'GET',
      headers
    };
  }
  
  class PDFNodeStreamFullReader extends BaseFullReader {
    constructor(stream) {
      super(stream);
  
      const handleResponse = response => {
        if (response.statusCode === 404) {
          const error = new _util.MissingPDFException(`Missing PDF "${this._url}".`);
          this._storedError = error;
  
          this._headersCapability.reject(error);
  
          return;
        }
  
        this._headersCapability.resolve();
  
        this._setReadableStream(response);
  
        const getResponseHeader = name => {
          return this._readableStream.headers[name.toLowerCase()];
        };
  
        const { allowRangeRequests, suggestedLength } = (0,
        _network_utils.validateRangeRequestCapabilities)({
          getResponseHeader,
          isHttp: stream.isHttp,
          rangeChunkSize: this._rangeChunkSize,
          disableRange: this._disableRange
        });
        this._isRangeSupported = allowRangeRequests;
        this._contentLength = suggestedLength || this._contentLength;
        this._filename = (0, _network_utils.extractFilenameFromHeader)(getResponseHeader);
      };
  
      this._request = null;
  
      if (this._url.protocol === 'http:') {
        this._request = http.request(
          createRequestOptions(this._url, stream.httpHeaders),
          handleResponse
        );
      } else {
        this._request = https.request(
          createRequestOptions(this._url, stream.httpHeaders),
          handleResponse
        );
      }
  
      this._request.on('error', reason => {
        this._storedError = reason;
  
        this._headersCapability.reject(reason);
      });
  
      this._request.end();
    }
  }
  
  class PDFNodeStreamRangeReader extends BaseRangeReader {
    constructor(stream, start, end) {
      super(stream);
      this._httpHeaders = {};
  
      for (const property in stream.httpHeaders) {
        const value = stream.httpHeaders[property];
  
        if (typeof value === 'undefined') {
          continue;
        }
  
        this._httpHeaders[property] = value;
      }
  
      this._httpHeaders.Range = `bytes=${start}-${end - 1}`;
  
      const handleResponse = response => {
        if (response.statusCode === 404) {
          const error = new _util.MissingPDFException(`Missing PDF "${this._url}".`);
          this._storedError = error;
          return;
        }
  
        this._setReadableStream(response);
      };
  
      this._request = null;
  
      if (this._url.protocol === 'http:') {
        this._request = http.request(
          createRequestOptions(this._url, this._httpHeaders),
          handleResponse
        );
      } else {
        this._request = https.request(
          createRequestOptions(this._url, this._httpHeaders),
          handleResponse
        );
      }
  
      this._request.on('error', reason => {
        this._storedError = reason;
      });
  
      this._request.end();
    }
  }
  
  class PDFNodeStreamFsFullReader extends BaseFullReader {
    constructor(stream) {
      super(stream);
      let path = decodeURIComponent(this._url.path);
  
      if (fileUriRegex.test(this._url.href)) {
        path = path.replace(/^\//, '');
      }
  
      fs.lstat(path, (error, stat) => {
        if (error) {
          if (error.code === 'ENOENT') {
            error = new _util.MissingPDFException(`Missing PDF "${path}".`);
          }
  
          this._storedError = error;
  
          this._headersCapability.reject(error);
  
          return;
        }
  
        this._contentLength = stat.size;
  
        this._setReadableStream(fs.createReadStream(path));
  
        this._headersCapability.resolve();
      });
    }
  }
  
  class PDFNodeStreamFsRangeReader extends BaseRangeReader {
    constructor(stream, start, end) {
      super(stream);
      let path = decodeURIComponent(this._url.path);
  
      if (fileUriRegex.test(this._url.href)) {
        path = path.replace(/^\//, '');
      }
  
      this._setReadableStream(
        fs.createReadStream(path, {
          start,
          end: end - 1
        })
      );
    }
  }
  