“use strict”;
const request = require(“request”); const EventEmitter = require(“events”).EventEmitter; const fs = require(“fs”); const URL = require(“whatwg-url”).URL;
const utils = require(“../utils”); const xhrSymbols = require(“./xmlhttprequest-symbols”);
function wrapCookieJarForRequest(cookieJar) {
const jarWrapper = request.jar(); jarWrapper._jar = cookieJar; return jarWrapper;
}
function getRequestHeader(requestHeaders, header) {
const lcHeader = header.toLowerCase(); const keys = Object.keys(requestHeaders); let n = keys.length; while (n--) { const key = keys[n]; if (key.toLowerCase() === lcHeader) { return requestHeaders[key]; } } return null;
}
function updateRequestHeader(requestHeaders, header, newValue) {
const lcHeader = header.toLowerCase(); const keys = Object.keys(requestHeaders); let n = keys.length; while (n--) { const key = keys[n]; if (key.toLowerCase() === lcHeader) { requestHeaders[key] = newValue; } }
}
const simpleMethods = new Set([“GET”, “HEAD”, “POST”]); const simpleHeaders = new Set([“accept”, “accept-language”, “content-language”, “content-type”]);
exports.getRequestHeader = getRequestHeader; exports.updateRequestHeader = updateRequestHeader; exports.simpleHeaders = simpleHeaders;
// return a “request” client object or an event emitter matching the same behaviour for unsupported protocols // the callback should be called with a “request” response object or an event emitter matching the same behaviour too exports.createClient = function createClient(xhr) {
const flag = xhr[xhrSymbols.flag]; const properties = xhr[xhrSymbols.properties]; const urlObj = new URL(flag.uri); const uri = urlObj.href; const ucMethod = flag.method.toUpperCase(); const requestManager = flag.requestManager; if (urlObj.protocol === "file:") { const response = new EventEmitter(); response.statusCode = 200; response.rawHeaders = []; response.headers = {}; response.request = { uri: urlObj }; const filePath = urlObj.pathname .replace(/^file:\/\//, "") .replace(/^\/([a-z]):\//i, "$1:/") .replace(/%20/g, " "); const client = new EventEmitter(); const readableStream = fs.createReadStream(filePath, { encoding: null }); readableStream.on("data", chunk => { response.emit("data", chunk); client.emit("data", chunk); }); readableStream.on("end", () => { response.emit("end"); client.emit("end"); }); readableStream.on("error", err => { response.emit("error", err); client.emit("error", err); }); client.abort = function () { readableStream.destroy(); client.emit("abort"); }; if (requestManager) { const req = { abort() { properties.abortError = true; xhr.abort(); } }; requestManager.add(req); const rmReq = requestManager.remove.bind(requestManager, req); client.on("abort", rmReq); client.on("error", rmReq); client.on("end", rmReq); } process.nextTick(() => client.emit("response", response)); return client; } if (urlObj.protocol === "data:") { const response = new EventEmitter(); response.request = { uri: urlObj }; const client = new EventEmitter(); let buffer; if (ucMethod === "GET") { try { const dataUrlContent = utils.parseDataUrl(uri); buffer = dataUrlContent.buffer; response.statusCode = 200; response.rawHeaders = dataUrlContent.type ? ["Content-Type", dataUrlContent.type] : []; response.headers = dataUrlContent.type ? { "content-type": dataUrlContent.type } : {}; } catch (err) { process.nextTick(() => client.emit("error", err)); return client; } } else { buffer = new Buffer(""); response.statusCode = 0; response.rawHeaders = {}; response.headers = {}; } client.abort = () => { // do nothing }; process.nextTick(() => { client.emit("response", response); process.nextTick(() => { response.emit("data", buffer); client.emit("data", buffer); response.emit("end"); client.emit("end"); }); }); return client; } const requestHeaders = {}; for (const header in flag.requestHeaders) { requestHeaders[header] = flag.requestHeaders[header]; } if (getRequestHeader(flag.requestHeaders, "referer") === null) { requestHeaders.Referer = flag.referrer; } if (getRequestHeader(flag.requestHeaders, "user-agent") === null) { requestHeaders["User-Agent"] = flag.userAgent; } if (getRequestHeader(flag.requestHeaders, "accept-language") === null) { requestHeaders["Accept-Language"] = "en"; } if (getRequestHeader(flag.requestHeaders, "accept") === null) { requestHeaders.Accept = "*/*"; } const crossOrigin = flag.origin !== urlObj.origin; if (crossOrigin) { requestHeaders.Origin = flag.origin; } const options = { uri, method: flag.method, headers: requestHeaders, gzip: true, maxRedirects: 21, followAllRedirects: true, encoding: null, pool: flag.pool, agentOptions: flag.agentOptions, strictSSL: flag.strictSSL }; if (flag.auth) { options.auth = { user: flag.auth.user || "", pass: flag.auth.pass || "", sendImmediately: false }; } if (flag.cookieJar && (!crossOrigin || flag.withCredentials)) { options.jar = wrapCookieJarForRequest(flag.cookieJar); } if (flag.proxy) { options.proxy = flag.proxy; } const body = flag.body; const hasBody = body !== undefined && body !== null && body !== "" && !(ucMethod === "HEAD" || ucMethod === "GET"); if (hasBody && !flag.formData) { options.body = body; } if (hasBody && getRequestHeader(flag.requestHeaders, "content-type") === null) { requestHeaders["Content-Type"] = "text/plain;charset=UTF-8"; } function doRequest() { try { const client = request(options); if (hasBody && flag.formData) { const form = client.form(); for (const entry of body) { form.append(entry.name, entry.value, entry.options); } } return client; } catch (e) { const client = new EventEmitter(); process.nextTick(() => client.emit("error", e)); return client; } } let client; const nonSimpleHeaders = Object.keys(flag.requestHeaders) .filter(header => !simpleHeaders.has(header.toLowerCase())); if (crossOrigin && (!simpleMethods.has(ucMethod) || nonSimpleHeaders.length > 0)) { client = new EventEmitter(); const preflightRequestHeaders = []; for (const header in requestHeaders) { preflightRequestHeaders[header] = requestHeaders[header]; } preflightRequestHeaders["Access-Control-Request-Method"] = flag.method; if (nonSimpleHeaders.length > 0) { preflightRequestHeaders["Access-Control-Request-Headers"] = nonSimpleHeaders.join(", "); } flag.preflight = true; const preflightOptions = { uri, method: "OPTIONS", headers: preflightRequestHeaders, followRedirect: false, encoding: null, pool: flag.pool, agentOptions: flag.agentOptions, strictSSL: flag.strictSSL }; if (flag.proxy) { preflightOptions.proxy = flag.proxy; } const preflightClient = request(preflightOptions); preflightClient.on("response", resp => { if (resp.statusCode >= 200 && resp.statusCode <= 299) { const realClient = doRequest(); realClient.on("response", res => client.emit("response", res)); realClient.on("data", chunk => client.emit("data", chunk)); realClient.on("end", () => client.emit("end")); realClient.on("abort", () => client.emit("abort")); realClient.on("request", req => { client.headers = realClient.headers; client.emit("request", req); }); realClient.on("redirect", () => { client.response = realClient.response; client.emit("redirect"); }); realClient.on("error", err => client.emit("error", err)); client.abort = () => { realClient.abort(); }; } else { client.emit("error", new Error("Response for preflight has invalid HTTP status code " + resp.statusCode)); } }); preflightClient.on("error", err => client.emit("error", err)); client.abort = () => { preflightClient.abort(); }; } else { client = doRequest(); } if (requestManager) { const req = { abort() { properties.abortError = true; xhr.abort(); } }; requestManager.add(req); const rmReq = requestManager.remove.bind(requestManager, req); client.on("abort", rmReq); client.on("error", rmReq); client.on("end", rmReq); } return client;
};