summaryrefslogtreecommitdiff
path: root/alarm/node_modules/jsdom/lib/jsdom/browser/resources/resource-loader.js
blob: 2af8d63ad984a85939171b7d72c8bf0052792fc0 (plain)
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
"use strict";
const fs = require("fs");
const { fileURLToPath } = require("url");
const { parseURL } = require("whatwg-url");
const dataURLFromRecord = require("data-urls").fromURLRecord;
const packageVersion = require("../../../../package.json").version;
const agentFactory = require("../../living/helpers/agent-factory");
const Request = require("../../living/helpers/http-request");

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;
  }

  _readDataURL(urlRecord) {
    const dataURL = dataURLFromRecord(urlRecord);
    let timeoutId;
    const promise = new Promise(resolve => {
      timeoutId = setTimeout(resolve, 0, dataURL.body);
    });
    promise.abort = () => {
      if (timeoutId !== undefined) {
        clearTimeout(timeoutId);
      }
    };
    return promise;
  }

  _readFile(filePath) {
    let readableStream, 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;
  }

  fetch(urlString, { accept, cookieJar, referrer } = {}) {
    const url = parseURL(urlString);

    if (!url) {
      return Promise.reject(new Error(`Tried to fetch invalid URL ${urlString}`));
    }

    switch (url.scheme) {
      case "data": {
        return this._readDataURL(url);
      }

      case "http":
      case "https": {
        const agents = agentFactory(this._proxy, this._strictSSL);
        const headers = {
          "User-Agent": this._userAgent,
          "Accept-Language": "en",
          "Accept-Encoding": "gzip",
          "Accept": accept || "*/*"
        };
        if (referrer && !IS_BROWSER) {
          headers.Referer = referrer;
        }
        const requestClient = new Request(
          urlString,
          { followRedirects: true, cookieJar, agents },
          { headers }
        );
        const promise = new Promise((resolve, reject) => {
          const accumulated = [];
          requestClient.once("response", res => {
            promise.response = res;
            const { statusCode } = res;
            // TODO This deviates from the spec when it comes to
            // loading resources such as images
            if (statusCode < 200 || statusCode > 299) {
              requestClient.abort();
              reject(new Error(`Resource was not loaded. Status: ${statusCode}`));
            }
          });
          requestClient.on("data", chunk => {
            accumulated.push(chunk);
          });
          requestClient.on("end", () => resolve(Buffer.concat(accumulated)));
          requestClient.on("error", reject);
        });
        // The method fromURL in lib/api.js crashes without the following four
        // properties defined on the Promise instance, causing the test suite to halt
        requestClient.on("end", () => {
          promise.href = requestClient.currentURL;
        });
        promise.abort = requestClient.abort.bind(requestClient);
        promise.getHeader = name => headers[name] || requestClient.getHeader(name);
        requestClient.end();
        return promise;
      }

      case "file": {
        try {
          return this._readFile(fileURLToPath(urlString));
        } catch (e) {
          return Promise.reject(e);
        }
      }

      default: {
        return Promise.reject(new Error(`Tried to fetch URL ${urlString} with invalid scheme ${url.scheme}`));
      }
    }
  }
};