Blame view

lib/jsdom/browser/resources/resource-loader.js 3.05 KB
858f2bdf5   Boyan Georgiev   fixes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
  "use strict";
  const fs = require("fs");
  const { parseURL } = require("whatwg-url");
  const dataURLFromRecord = require("data-urls").fromURLRecord;
  const request = require("request-promise-native");
  const wrapCookieJarForRequest = require("../../living/helpers/wrap-cookie-jar-for-request");
  const packageVersion = require("../../../../package.json").version;
  const IS_BROWSER = Object.prototype.toString.call(process) !== "[object process]";
  
  module.exports = class ResourceLoader {
    constructor({
      strictSSL = true,
      proxy = undefined,
      userAgent = `Mozilla/5.0 (${process.platform || "unknown OS"}) AppleWebKit/537.36 ` +
                  `(KHTML, like Gecko) jsdom/${packageVersion}`
    } = {}) {
      this._strictSSL = strictSSL;
      this._proxy = proxy;
      this._userAgent = userAgent;
    }
  
    _readFile(filePath) {
      let readableStream;
      let abort; // Native Promises doesn't have an "abort" method.
  
      /*
       * Creating a promise for two reason:
       *   1. fetch always return a promise.
       *   2. We need to add an abort handler.
      */
      const promise = new Promise((resolve, reject) => {
        readableStream = fs.createReadStream(filePath);
        let data = Buffer.alloc(0);
  
        abort = reject;
  
        readableStream.on("error", reject);
  
        readableStream.on("data", chunk => {
          data = Buffer.concat([data, chunk]);
        });
  
        readableStream.on("end", () => {
          resolve(data);
        });
      });
  
      promise.abort = () => {
        readableStream.destroy();
        const error = new Error("request canceled by user");
        error.isAbortError = true;
        abort(error);
      };
  
      return promise;
    }
  
    _getRequestOptions({ cookieJar, referrer, accept = "*/*" }) {
      const requestOptions = {
        encoding: null,
        gzip: true,
        jar: wrapCookieJarForRequest(cookieJar),
        strictSSL: this._strictSSL,
        proxy: this._proxy,
        forever: true,
        headers: {
          "User-Agent": this._userAgent,
          "Accept-Language": "en",
          Accept: accept
        }
      };
  
      if (referrer && !IS_BROWSER) {
        requestOptions.headers.referer = referrer;
      }
  
      return requestOptions;
    }
  
    fetch(urlString, options = {}) {
      const url = parseURL(urlString);
  
      if (!url) {
        return Promise.reject(new Error(`Tried to fetch invalid URL ${urlString}`));
      }
  
      switch (url.scheme) {
        case "data": {
          return Promise.resolve(dataURLFromRecord(url).body);
        }
  
        case "http":
        case "https": {
          const requestOptions = this._getRequestOptions(options);
          return request(urlString, requestOptions);
        }
  
        case "file": {
          // TODO: Improve the URL => file algorithm. See https://github.com/jsdom/jsdom/pull/2279#discussion_r199977987
          const filePath = urlString
            .replace(/^file:\/\//, "")
            .replace(/^\/([a-z]):\//i, "$1:/")
            .replace(/%20/g, " ");
  
          return this._readFile(filePath);
        }
  
        default: {
          return Promise.reject(new Error(`Tried to fetch URL ${urlString} with invalid scheme ${url.scheme}`));
        }
      }
    }
  };