"use strict"; const http = require("http"); const https = require("https"); const { URL } = require('url'); const querystring = require("querystring"); const stream = require("stream"); const zlib = require('zlib'); function Cookie(rawCookie) { var cookieRegex = /([^,; =]+)(?:=([^;]*)(?:[ ;]+|$))/g; var match; var cookie = {}; while ((match = cookieRegex.exec(rawCookie)) !== null) { var key = match[1]; var value = match[2]; switch (key.toLowerCase()) { case "path" : cookie.path = value; break; case "domain" : cookie.domain = value; break; case "expires" : cookie.expires = Date.parse(value); break; case "max-age" : cookie.maxAge = Number.parseInt(value); break; case "secured" : cookie.secured = true; break; case "http-only" : cookie.httpOnly = true; break; default : cookie.name = key; cookie.value = value; break; } } return cookie; } var CookieJar = function () { var cookies = new Map(); this.get = function (path) { var cookieString = ""; if (cookies.has(path)) { var currentDate = new Date(); cookies.get(path).forEach( function (value, key, map) { if (value.expires !== undefined && value.expires < currentDate || value.maxAge !== undefined && currentDate.setSeconds(currentDate.getSeconds() + value.maxAge) < new Date()) { map.delete(key); } else { cookieString += value.name + "=" + value.value +"; "; } }, null); } return cookieString; }; this.update = function (_cookie) { var cookie = Cookie(_cookie); var path = cookie.path ? cookie.path.toUpperCase() : "/"; if (!cookies.has(path)) { cookies.set(path, new Map()); } var deleteCookie = cookie.maxAge !== undefined && cookie.maxAge <= 0 || cookie.expires !== undefined && cookie.expires < new Date(); if (deleteCookie) { if (cookies.get(path).has(cookie.name)) { cookies.get(path).delete(cookie.name); } } else if (!cookies.get(path).has(cookie.name)) { cookies.get(path).set(cookie.name, cookie); } else { cookies.get(path).set(cookie.name, cookie); } }; }; module.exports = function Browser (parameters) { parameters = parameters || {}; var baseUrl = parameters.baseUrl ? new URL(parameters.baseUrl) : null; //CookieJar and custom headers var cookieJar = new CookieJar(); this.headers = parameters.headers || {}; this.redirect = parameters && parameters.redirect; this.ignoreErrors = parameters && parameters.ignoreErrors; if (this.headers["User-Agent"] === undefined) { this.headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0"; } if (this.headers["Accept-Language"] === undefined) { this.headers["Accept-Language"] = "en,*:q=0.1"; } if (this.headers["Accept"] === undefined) { this.headers["Accept"] = "*/*"; } if (this.headers["Accept-Encoding"] === undefined) { this.headers["Accept-Encoding"] = "gzip,deflate"; } if (this.headers["Accept-Charset"] === undefined) { this.headers["Accept-Charset"] = "us-ascii,ISO-8859-1,ISO-8859-2,ISO-8859-3,ISO-8859-4,ISO-8859-5,ISO-8859-6,ISO-8859-7,ISO-8859-8,ISO-8859-9,ISO-8859-10,ISO-8859-13,ISO-8859-14,ISO-8859-15,ISO-8859-16,windows-1250,windows-1251,windows-1252,windows-1256,windows-1257,cp437,cp737,cp850,cp852,cp866,x-cp866-u,x-mac,x-mac-ce,x-kam-cs,koi8-r,koi8-u,koi8-ru,TCVN-5712,VISCII,utf-8"; } if (parameters.keepAlive) { this.headers["Connection"] = "keep-alive"; } this.get = function (path, querystring) { return query({ method : "GET", path: path, querystring: querystring }, this); }; this.post = function (path, data) { return query({ method : "POST", path: path, data: data }, this); }; this.postForm = function (path, data) { return query({ method : "POST", path: path, data: querystring.stringify(data) }, this); }; this.put = function (path, data) { return query({ method : "PUT", path: path, data: data }, this); }; this.delete = function (path, querystring) { return query({ method : "DELETE", path: path, querystring: querystring }, this); }; this.patch = function (path, data) { return query({ method : "PATCH", path: path, data: data }, this); }; function query(_request, client) { var url = new URL(_request.path); var isHttps = url.protocol === "https:"; var requestObj = { hostname: url.hostname, port: url.port ? url.port : isHttps ? 443 : 80, path: url.pathname, method: _request.method, headers: client.headers }; if (url.search) { requestObj.path += url.search; } if (_request.querystring) { let encodedQueryString = querystring.stringify(_request.querystring); requestObj.path += url.search ? url.search + "&" + encodedQueryString : "?" + encodedQueryString; } var cookies = cookieJar.get(url.pathname); if (url.pathname !== "/") { cookies += cookieJar.get("/"); } if (cookies) { requestObj.headers.Cookie = cookies; } if (isHttps) { requestObj.rejectUnauthorized = false; } //Complete the url with the base one if a path was provided // if (client.baseUrl !== null && pathUrl.protocol === null) { // pathUrl.protocol = baseUrl.protocol; // pathUrl.hostname = baseUrl.hostname; // pathUrl.auth = baseUrl.auth; // pathUrl.port = baseUrl.port; // } return new Promise((fulfill, reject) => { var protocol = isHttps ? https : http; var request = protocol.request(requestObj, function(response) { if (response.headers["set-cookie"]) { for (let i = 0; i < response.headers["set-cookie"].length; ++i) { cookieJar.update(response.headers["set-cookie"][i]); } } if (response.statusCode < 300 || response.statusCode < 400 && !client.redirect || response.statusCode >= 400 && client.ignoreErrors) { let decoder; if (response.headers["content-encoding"] === "gzip") { decoder = zlib.createGunzip(); response.pipe(decoder); } else if (response.headers["content-encoding"] === "deflate") { decoder = zlib.createInfate(); response.pipe(decoder); } else { decoder = response; } fulfill({ response: response, body: decoder, url: url.toString() }); } else if (response.statusCode < 400 && client.redirect) { client.headers["Referer"] = url.protocol + "//" + url.hostname + url.pathname; let newLocation = response.headers.location; if (newLocation.startsWith("/")) { newLocation = url.protocol + "//" + url.hostname + newLocation; } client.get(newLocation) .then(fulfill) .catch(reject); } else { reject(response.statusCode, response); } }); request.on("error", (error) => { reject(error); }); if (_request.data) { if (_request.data instanceof stream) { _request.data.pipe(request); } else if (_request.data instanceof String || typeof _request.data === "string") { request.write(_request.data); } else { request.write(JSON.stringify(_request.data)); } } request.end(); }); } }; module.exports.readPage = function (data) { return new Promise( function ( fulfill, reject) { var page = ""; data.on("data", function (text) { page += text; }); data.on("end", function () { fulfill(page); }); data.on("error", function (error) { reject(error); }); }); };