UNPKG

11.3 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.PDFNodeStream = void 0;
28
29var _util = require("../shared/util.js");
30
31var _network_utils = require("./network_utils.js");
32
33;
34
35const fs = require("fs");
36
37const http = require("http");
38
39const https = require("https");
40
41const url = require("url");
42
43const fileUriRegex = /^file:\/\/\/[a-zA-Z]:\//;
44
45function parseUrl(sourceUrl) {
46 const parsedUrl = url.parse(sourceUrl);
47
48 if (parsedUrl.protocol === "file:" || parsedUrl.host) {
49 return parsedUrl;
50 }
51
52 if (/^[a-z]:[/\\]/i.test(sourceUrl)) {
53 return url.parse(`file:///${sourceUrl}`);
54 }
55
56 if (!parsedUrl.host) {
57 parsedUrl.protocol = "file:";
58 }
59
60 return parsedUrl;
61}
62
63class PDFNodeStream {
64 constructor(source) {
65 this.source = source;
66 this.url = parseUrl(source.url);
67 this.isHttp = this.url.protocol === "http:" || this.url.protocol === "https:";
68 this.isFsUrl = this.url.protocol === "file:";
69 this.httpHeaders = this.isHttp && source.httpHeaders || {};
70 this._fullRequestReader = null;
71 this._rangeRequestReaders = [];
72 }
73
74 get _progressiveDataLength() {
75 return this._fullRequestReader?._loaded ?? 0;
76 }
77
78 getFullReader() {
79 (0, _util.assert)(!this._fullRequestReader, "PDFNodeStream.getFullReader can only be called once.");
80 this._fullRequestReader = this.isFsUrl ? new PDFNodeStreamFsFullReader(this) : new PDFNodeStreamFullReader(this);
81 return this._fullRequestReader;
82 }
83
84 getRangeReader(start, end) {
85 if (end <= this._progressiveDataLength) {
86 return null;
87 }
88
89 const rangeReader = this.isFsUrl ? new PDFNodeStreamFsRangeReader(this, start, end) : new PDFNodeStreamRangeReader(this, start, end);
90
91 this._rangeRequestReaders.push(rangeReader);
92
93 return rangeReader;
94 }
95
96 cancelAllRequests(reason) {
97 if (this._fullRequestReader) {
98 this._fullRequestReader.cancel(reason);
99 }
100
101 for (const reader of this._rangeRequestReaders.slice(0)) {
102 reader.cancel(reason);
103 }
104 }
105
106}
107
108exports.PDFNodeStream = PDFNodeStream;
109
110class BaseFullReader {
111 constructor(stream) {
112 this._url = stream.url;
113 this._done = false;
114 this._storedError = null;
115 this.onProgress = null;
116 const source = stream.source;
117 this._contentLength = source.length;
118 this._loaded = 0;
119 this._filename = null;
120 this._disableRange = source.disableRange || false;
121 this._rangeChunkSize = source.rangeChunkSize;
122
123 if (!this._rangeChunkSize && !this._disableRange) {
124 this._disableRange = true;
125 }
126
127 this._isStreamingSupported = !source.disableStream;
128 this._isRangeSupported = !source.disableRange;
129 this._readableStream = null;
130 this._readCapability = (0, _util.createPromiseCapability)();
131 this._headersCapability = (0, _util.createPromiseCapability)();
132 }
133
134 get headersReady() {
135 return this._headersCapability.promise;
136 }
137
138 get filename() {
139 return this._filename;
140 }
141
142 get contentLength() {
143 return this._contentLength;
144 }
145
146 get isRangeSupported() {
147 return this._isRangeSupported;
148 }
149
150 get isStreamingSupported() {
151 return this._isStreamingSupported;
152 }
153
154 async read() {
155 await this._readCapability.promise;
156
157 if (this._done) {
158 return {
159 value: undefined,
160 done: true
161 };
162 }
163
164 if (this._storedError) {
165 throw this._storedError;
166 }
167
168 const chunk = this._readableStream.read();
169
170 if (chunk === null) {
171 this._readCapability = (0, _util.createPromiseCapability)();
172 return this.read();
173 }
174
175 this._loaded += chunk.length;
176
177 if (this.onProgress) {
178 this.onProgress({
179 loaded: this._loaded,
180 total: this._contentLength
181 });
182 }
183
184 const buffer = new Uint8Array(chunk).buffer;
185 return {
186 value: buffer,
187 done: false
188 };
189 }
190
191 cancel(reason) {
192 if (!this._readableStream) {
193 this._error(reason);
194
195 return;
196 }
197
198 this._readableStream.destroy(reason);
199 }
200
201 _error(reason) {
202 this._storedError = reason;
203
204 this._readCapability.resolve();
205 }
206
207 _setReadableStream(readableStream) {
208 this._readableStream = readableStream;
209 readableStream.on("readable", () => {
210 this._readCapability.resolve();
211 });
212 readableStream.on("end", () => {
213 readableStream.destroy();
214 this._done = true;
215
216 this._readCapability.resolve();
217 });
218 readableStream.on("error", reason => {
219 this._error(reason);
220 });
221
222 if (!this._isStreamingSupported && this._isRangeSupported) {
223 this._error(new _util.AbortException("streaming is disabled"));
224 }
225
226 if (this._storedError) {
227 this._readableStream.destroy(this._storedError);
228 }
229 }
230
231}
232
233class BaseRangeReader {
234 constructor(stream) {
235 this._url = stream.url;
236 this._done = false;
237 this._storedError = null;
238 this.onProgress = null;
239 this._loaded = 0;
240 this._readableStream = null;
241 this._readCapability = (0, _util.createPromiseCapability)();
242 const source = stream.source;
243 this._isStreamingSupported = !source.disableStream;
244 }
245
246 get isStreamingSupported() {
247 return this._isStreamingSupported;
248 }
249
250 async read() {
251 await this._readCapability.promise;
252
253 if (this._done) {
254 return {
255 value: undefined,
256 done: true
257 };
258 }
259
260 if (this._storedError) {
261 throw this._storedError;
262 }
263
264 const chunk = this._readableStream.read();
265
266 if (chunk === null) {
267 this._readCapability = (0, _util.createPromiseCapability)();
268 return this.read();
269 }
270
271 this._loaded += chunk.length;
272
273 if (this.onProgress) {
274 this.onProgress({
275 loaded: this._loaded
276 });
277 }
278
279 const buffer = new Uint8Array(chunk).buffer;
280 return {
281 value: buffer,
282 done: false
283 };
284 }
285
286 cancel(reason) {
287 if (!this._readableStream) {
288 this._error(reason);
289
290 return;
291 }
292
293 this._readableStream.destroy(reason);
294 }
295
296 _error(reason) {
297 this._storedError = reason;
298
299 this._readCapability.resolve();
300 }
301
302 _setReadableStream(readableStream) {
303 this._readableStream = readableStream;
304 readableStream.on("readable", () => {
305 this._readCapability.resolve();
306 });
307 readableStream.on("end", () => {
308 readableStream.destroy();
309 this._done = true;
310
311 this._readCapability.resolve();
312 });
313 readableStream.on("error", reason => {
314 this._error(reason);
315 });
316
317 if (this._storedError) {
318 this._readableStream.destroy(this._storedError);
319 }
320 }
321
322}
323
324function createRequestOptions(parsedUrl, headers) {
325 return {
326 protocol: parsedUrl.protocol,
327 auth: parsedUrl.auth,
328 host: parsedUrl.hostname,
329 port: parsedUrl.port,
330 path: parsedUrl.path,
331 method: "GET",
332 headers
333 };
334}
335
336class PDFNodeStreamFullReader extends BaseFullReader {
337 constructor(stream) {
338 super(stream);
339
340 const handleResponse = response => {
341 if (response.statusCode === 404) {
342 const error = new _util.MissingPDFException(`Missing PDF "${this._url}".`);
343 this._storedError = error;
344
345 this._headersCapability.reject(error);
346
347 return;
348 }
349
350 this._headersCapability.resolve();
351
352 this._setReadableStream(response);
353
354 const getResponseHeader = name => {
355 return this._readableStream.headers[name.toLowerCase()];
356 };
357
358 const {
359 allowRangeRequests,
360 suggestedLength
361 } = (0, _network_utils.validateRangeRequestCapabilities)({
362 getResponseHeader,
363 isHttp: stream.isHttp,
364 rangeChunkSize: this._rangeChunkSize,
365 disableRange: this._disableRange
366 });
367 this._isRangeSupported = allowRangeRequests;
368 this._contentLength = suggestedLength || this._contentLength;
369 this._filename = (0, _network_utils.extractFilenameFromHeader)(getResponseHeader);
370 };
371
372 this._request = null;
373
374 if (this._url.protocol === "http:") {
375 this._request = http.request(createRequestOptions(this._url, stream.httpHeaders), handleResponse);
376 } else {
377 this._request = https.request(createRequestOptions(this._url, stream.httpHeaders), handleResponse);
378 }
379
380 this._request.on("error", reason => {
381 this._storedError = reason;
382
383 this._headersCapability.reject(reason);
384 });
385
386 this._request.end();
387 }
388
389}
390
391class PDFNodeStreamRangeReader extends BaseRangeReader {
392 constructor(stream, start, end) {
393 super(stream);
394 this._httpHeaders = {};
395
396 for (const property in stream.httpHeaders) {
397 const value = stream.httpHeaders[property];
398
399 if (typeof value === "undefined") {
400 continue;
401 }
402
403 this._httpHeaders[property] = value;
404 }
405
406 this._httpHeaders.Range = `bytes=${start}-${end - 1}`;
407
408 const handleResponse = response => {
409 if (response.statusCode === 404) {
410 const error = new _util.MissingPDFException(`Missing PDF "${this._url}".`);
411 this._storedError = error;
412 return;
413 }
414
415 this._setReadableStream(response);
416 };
417
418 this._request = null;
419
420 if (this._url.protocol === "http:") {
421 this._request = http.request(createRequestOptions(this._url, this._httpHeaders), handleResponse);
422 } else {
423 this._request = https.request(createRequestOptions(this._url, this._httpHeaders), handleResponse);
424 }
425
426 this._request.on("error", reason => {
427 this._storedError = reason;
428 });
429
430 this._request.end();
431 }
432
433}
434
435class PDFNodeStreamFsFullReader extends BaseFullReader {
436 constructor(stream) {
437 super(stream);
438 let path = decodeURIComponent(this._url.path);
439
440 if (fileUriRegex.test(this._url.href)) {
441 path = path.replace(/^\//, "");
442 }
443
444 fs.lstat(path, (error, stat) => {
445 if (error) {
446 if (error.code === "ENOENT") {
447 error = new _util.MissingPDFException(`Missing PDF "${path}".`);
448 }
449
450 this._storedError = error;
451
452 this._headersCapability.reject(error);
453
454 return;
455 }
456
457 this._contentLength = stat.size;
458
459 this._setReadableStream(fs.createReadStream(path));
460
461 this._headersCapability.resolve();
462 });
463 }
464
465}
466
467class PDFNodeStreamFsRangeReader extends BaseRangeReader {
468 constructor(stream, start, end) {
469 super(stream);
470 let path = decodeURIComponent(this._url.path);
471
472 if (fileUriRegex.test(this._url.href)) {
473 path = path.replace(/^\//, "");
474 }
475
476 this._setReadableStream(fs.createReadStream(path, {
477 start,
478 end: end - 1
479 }));
480 }
481
482}
\No newline at end of file