uniapp,h5

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685
  1. /**
  2. * Wrapper for built-in http.js to emulate the browser XMLHttpRequest object.
  3. *
  4. * This can be used with JS designed for browsers to improve reuse of code and
  5. * allow the use of existing libraries.
  6. *
  7. * Usage: include("XMLHttpRequest.js") and use XMLHttpRequest per W3C specs.
  8. *
  9. * @author Dan DeFelippi <dan@driverdan.com>
  10. * @contributor David Ellis <d.f.ellis@ieee.org>
  11. * @license MIT
  12. */
  13. var fs = require('fs');
  14. var Url = require('url');
  15. var spawn = require('child_process').spawn;
  16. /**
  17. * Module exports.
  18. */
  19. module.exports = XMLHttpRequest;
  20. // backwards-compat
  21. XMLHttpRequest.XMLHttpRequest = XMLHttpRequest;
  22. /**
  23. * `XMLHttpRequest` constructor.
  24. *
  25. * Supported options for the `opts` object are:
  26. *
  27. * - `agent`: An http.Agent instance; http.globalAgent may be used; if 'undefined', agent usage is disabled
  28. *
  29. * @param {Object} opts optional "options" object
  30. */
  31. function XMLHttpRequest(opts) {
  32. "use strict";
  33. opts = opts || {};
  34. /**
  35. * Private variables
  36. */
  37. var self = this;
  38. var http = require('http');
  39. var https = require('https');
  40. // Holds http.js objects
  41. var request;
  42. var response;
  43. // Request settings
  44. var settings = {};
  45. // Disable header blacklist.
  46. // Not part of XHR specs.
  47. var disableHeaderCheck = false;
  48. // Set some default headers
  49. var defaultHeaders = {
  50. "User-Agent": "node-XMLHttpRequest",
  51. "Accept": "*/*"
  52. };
  53. var headers = Object.assign({}, defaultHeaders);
  54. // These headers are not user setable.
  55. // The following are allowed but banned in the spec:
  56. // * user-agent
  57. var forbiddenRequestHeaders = [
  58. "accept-charset",
  59. "accept-encoding",
  60. "access-control-request-headers",
  61. "access-control-request-method",
  62. "connection",
  63. "content-length",
  64. "content-transfer-encoding",
  65. "cookie",
  66. "cookie2",
  67. "date",
  68. "expect",
  69. "host",
  70. "keep-alive",
  71. "origin",
  72. "referer",
  73. "te",
  74. "trailer",
  75. "transfer-encoding",
  76. "upgrade",
  77. "via"
  78. ];
  79. // These request methods are not allowed
  80. var forbiddenRequestMethods = [
  81. "TRACE",
  82. "TRACK",
  83. "CONNECT"
  84. ];
  85. // Send flag
  86. var sendFlag = false;
  87. // Error flag, used when errors occur or abort is called
  88. var errorFlag = false;
  89. var abortedFlag = false;
  90. // Event listeners
  91. var listeners = {};
  92. /**
  93. * Constants
  94. */
  95. this.UNSENT = 0;
  96. this.OPENED = 1;
  97. this.HEADERS_RECEIVED = 2;
  98. this.LOADING = 3;
  99. this.DONE = 4;
  100. /**
  101. * Public vars
  102. */
  103. // Current state
  104. this.readyState = this.UNSENT;
  105. // default ready state change handler in case one is not set or is set late
  106. this.onreadystatechange = null;
  107. // Result & response
  108. this.responseText = "";
  109. this.responseXML = "";
  110. this.response = Buffer.alloc(0);
  111. this.status = null;
  112. this.statusText = null;
  113. /**
  114. * Private methods
  115. */
  116. /**
  117. * Check if the specified header is allowed.
  118. *
  119. * @param string header Header to validate
  120. * @return boolean False if not allowed, otherwise true
  121. */
  122. var isAllowedHttpHeader = function(header) {
  123. return disableHeaderCheck || (header && forbiddenRequestHeaders.indexOf(header.toLowerCase()) === -1);
  124. };
  125. /**
  126. * Check if the specified method is allowed.
  127. *
  128. * @param string method Request method to validate
  129. * @return boolean False if not allowed, otherwise true
  130. */
  131. var isAllowedHttpMethod = function(method) {
  132. return (method && forbiddenRequestMethods.indexOf(method) === -1);
  133. };
  134. /**
  135. * Public methods
  136. */
  137. /**
  138. * Open the connection. Currently supports local server requests.
  139. *
  140. * @param string method Connection method (eg GET, POST)
  141. * @param string url URL for the connection.
  142. * @param boolean async Asynchronous connection. Default is true.
  143. * @param string user Username for basic authentication (optional)
  144. * @param string password Password for basic authentication (optional)
  145. */
  146. this.open = function(method, url, async, user, password) {
  147. this.abort();
  148. errorFlag = false;
  149. abortedFlag = false;
  150. // Check for valid request method
  151. if (!isAllowedHttpMethod(method)) {
  152. throw new Error("SecurityError: Request method not allowed");
  153. }
  154. settings = {
  155. "method": method,
  156. "url": url.toString(),
  157. "async": (typeof async !== "boolean" ? true : async),
  158. "user": user || null,
  159. "password": password || null
  160. };
  161. setState(this.OPENED);
  162. };
  163. /**
  164. * Disables or enables isAllowedHttpHeader() check the request. Enabled by default.
  165. * This does not conform to the W3C spec.
  166. *
  167. * @param boolean state Enable or disable header checking.
  168. */
  169. this.setDisableHeaderCheck = function(state) {
  170. disableHeaderCheck = state;
  171. };
  172. /**
  173. * Sets a header for the request.
  174. *
  175. * @param string header Header name
  176. * @param string value Header value
  177. * @return boolean Header added
  178. */
  179. this.setRequestHeader = function(header, value) {
  180. if (this.readyState != this.OPENED) {
  181. throw new Error("INVALID_STATE_ERR: setRequestHeader can only be called when state is OPEN");
  182. }
  183. if (!isAllowedHttpHeader(header)) {
  184. console.warn('Refused to set unsafe header "' + header + '"');
  185. return false;
  186. }
  187. if (sendFlag) {
  188. throw new Error("INVALID_STATE_ERR: send flag is true");
  189. }
  190. headers[header] = value;
  191. return true;
  192. };
  193. /**
  194. * Gets a header from the server response.
  195. *
  196. * @param string header Name of header to get.
  197. * @return string Text of the header or null if it doesn't exist.
  198. */
  199. this.getResponseHeader = function(header) {
  200. if (typeof header === "string"
  201. && this.readyState > this.OPENED
  202. && response.headers[header.toLowerCase()]
  203. && !errorFlag
  204. ) {
  205. return response.headers[header.toLowerCase()];
  206. }
  207. return null;
  208. };
  209. /**
  210. * Gets all the response headers.
  211. *
  212. * @return string A string with all response headers separated by CR+LF
  213. */
  214. this.getAllResponseHeaders = function() {
  215. if (this.readyState < this.HEADERS_RECEIVED || errorFlag) {
  216. return "";
  217. }
  218. var result = "";
  219. for (var i in response.headers) {
  220. // Cookie headers are excluded
  221. if (i !== "set-cookie" && i !== "set-cookie2") {
  222. result += i + ": " + response.headers[i] + "\r\n";
  223. }
  224. }
  225. return result.substr(0, result.length - 2);
  226. };
  227. /**
  228. * Gets a request header
  229. *
  230. * @param string name Name of header to get
  231. * @return string Returns the request header or empty string if not set
  232. */
  233. this.getRequestHeader = function(name) {
  234. // @TODO Make this case insensitive
  235. if (typeof name === "string" && headers[name]) {
  236. return headers[name];
  237. }
  238. return "";
  239. };
  240. /**
  241. * Sends the request to the server.
  242. *
  243. * @param string data Optional data to send as request body.
  244. */
  245. this.send = function(data) {
  246. if (this.readyState != this.OPENED) {
  247. throw new Error("INVALID_STATE_ERR: connection must be opened before send() is called");
  248. }
  249. if (sendFlag) {
  250. throw new Error("INVALID_STATE_ERR: send has already been called");
  251. }
  252. var ssl = false, local = false;
  253. var url = Url.parse(settings.url);
  254. var host;
  255. // Determine the server
  256. switch (url.protocol) {
  257. case 'https:':
  258. ssl = true;
  259. // SSL & non-SSL both need host, no break here.
  260. case 'http:':
  261. host = url.hostname;
  262. break;
  263. case 'file:':
  264. local = true;
  265. break;
  266. case undefined:
  267. case '':
  268. host = "localhost";
  269. break;
  270. default:
  271. throw new Error("Protocol not supported.");
  272. }
  273. // Load files off the local filesystem (file://)
  274. if (local) {
  275. if (settings.method !== "GET") {
  276. throw new Error("XMLHttpRequest: Only GET method is supported");
  277. }
  278. if (settings.async) {
  279. fs.readFile(unescape(url.pathname), function(error, data) {
  280. if (error) {
  281. self.handleError(error, error.errno || -1);
  282. } else {
  283. self.status = 200;
  284. self.responseText = data.toString('utf8');
  285. self.response = data;
  286. setState(self.DONE);
  287. }
  288. });
  289. } else {
  290. try {
  291. this.response = fs.readFileSync(unescape(url.pathname));
  292. this.responseText = this.response.toString('utf8');
  293. this.status = 200;
  294. setState(self.DONE);
  295. } catch(e) {
  296. this.handleError(e, e.errno || -1);
  297. }
  298. }
  299. return;
  300. }
  301. // Default to port 80. If accessing localhost on another port be sure
  302. // to use http://localhost:port/path
  303. var port = url.port || (ssl ? 443 : 80);
  304. // Add query string if one is used
  305. var uri = url.pathname + (url.search ? url.search : '');
  306. // Set the Host header or the server may reject the request
  307. headers["Host"] = host;
  308. if (!((ssl && port === 443) || port === 80)) {
  309. headers["Host"] += ':' + url.port;
  310. }
  311. // Set Basic Auth if necessary
  312. if (settings.user) {
  313. if (typeof settings.password == "undefined") {
  314. settings.password = "";
  315. }
  316. var authBuf = new Buffer(settings.user + ":" + settings.password);
  317. headers["Authorization"] = "Basic " + authBuf.toString("base64");
  318. }
  319. // Set content length header
  320. if (settings.method === "GET" || settings.method === "HEAD") {
  321. data = null;
  322. } else if (data) {
  323. headers["Content-Length"] = Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data);
  324. if (!headers["Content-Type"]) {
  325. headers["Content-Type"] = "text/plain;charset=UTF-8";
  326. }
  327. } else if (settings.method === "POST") {
  328. // For a post with no data set Content-Length: 0.
  329. // This is required by buggy servers that don't meet the specs.
  330. headers["Content-Length"] = 0;
  331. }
  332. var agent = opts.agent || false;
  333. var options = {
  334. host: host,
  335. port: port,
  336. path: uri,
  337. method: settings.method,
  338. headers: headers,
  339. agent: agent
  340. };
  341. if (ssl) {
  342. options.pfx = opts.pfx;
  343. options.key = opts.key;
  344. options.passphrase = opts.passphrase;
  345. options.cert = opts.cert;
  346. options.ca = opts.ca;
  347. options.ciphers = opts.ciphers;
  348. options.rejectUnauthorized = opts.rejectUnauthorized === false ? false : true;
  349. }
  350. // Reset error flag
  351. errorFlag = false;
  352. // Handle async requests
  353. if (settings.async) {
  354. // Use the proper protocol
  355. var doRequest = ssl ? https.request : http.request;
  356. // Request is being sent, set send flag
  357. sendFlag = true;
  358. // As per spec, this is called here for historical reasons.
  359. self.dispatchEvent("readystatechange");
  360. // Handler for the response
  361. var responseHandler = function(resp) {
  362. // Set response var to the response we got back
  363. // This is so it remains accessable outside this scope
  364. response = resp;
  365. // Check for redirect
  366. // @TODO Prevent looped redirects
  367. if (response.statusCode === 302 || response.statusCode === 303 || response.statusCode === 307) {
  368. // Change URL to the redirect location
  369. settings.url = response.headers.location;
  370. var url = Url.parse(settings.url);
  371. // Set host var in case it's used later
  372. host = url.hostname;
  373. // Options for the new request
  374. var newOptions = {
  375. hostname: url.hostname,
  376. port: url.port,
  377. path: url.path,
  378. method: response.statusCode === 303 ? 'GET' : settings.method,
  379. headers: headers
  380. };
  381. if (ssl) {
  382. newOptions.pfx = opts.pfx;
  383. newOptions.key = opts.key;
  384. newOptions.passphrase = opts.passphrase;
  385. newOptions.cert = opts.cert;
  386. newOptions.ca = opts.ca;
  387. newOptions.ciphers = opts.ciphers;
  388. newOptions.rejectUnauthorized = opts.rejectUnauthorized === false ? false : true;
  389. }
  390. // Issue the new request
  391. request = doRequest(newOptions, responseHandler).on('error', errorHandler);
  392. request.end();
  393. // @TODO Check if an XHR event needs to be fired here
  394. return;
  395. }
  396. setState(self.HEADERS_RECEIVED);
  397. self.status = response.statusCode;
  398. response.on('data', function(chunk) {
  399. // Make sure there's some data
  400. if (chunk) {
  401. var data = Buffer.from(chunk);
  402. self.response = Buffer.concat([self.response, data]);
  403. }
  404. // Don't emit state changes if the connection has been aborted.
  405. if (sendFlag) {
  406. setState(self.LOADING);
  407. }
  408. });
  409. response.on('end', function() {
  410. if (sendFlag) {
  411. // The sendFlag needs to be set before setState is called. Otherwise if we are chaining callbacks
  412. // there can be a timing issue (the callback is called and a new call is made before the flag is reset).
  413. sendFlag = false;
  414. // Discard the 'end' event if the connection has been aborted
  415. setState(self.DONE);
  416. // Construct responseText from response
  417. self.responseText = self.response.toString('utf8');
  418. }
  419. });
  420. response.on('error', function(error) {
  421. self.handleError(error);
  422. });
  423. }
  424. // Error handler for the request
  425. var errorHandler = function(error) {
  426. self.handleError(error);
  427. }
  428. // Create the request
  429. request = doRequest(options, responseHandler).on('error', errorHandler);
  430. if (opts.autoUnref) {
  431. request.on('socket', (socket) => {
  432. socket.unref();
  433. });
  434. }
  435. // Node 0.4 and later won't accept empty data. Make sure it's needed.
  436. if (data) {
  437. request.write(data);
  438. }
  439. request.end();
  440. self.dispatchEvent("loadstart");
  441. } else { // Synchronous
  442. // Create a temporary file for communication with the other Node process
  443. var contentFile = ".node-xmlhttprequest-content-" + process.pid;
  444. var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
  445. fs.writeFileSync(syncFile, "", "utf8");
  446. // The async request the other Node process executes
  447. var execString = "var http = require('http'), https = require('https'), fs = require('fs');"
  448. + "var doRequest = http" + (ssl ? "s" : "") + ".request;"
  449. + "var options = " + JSON.stringify(options) + ";"
  450. + "var responseText = '';"
  451. + "var responseData = Buffer.alloc(0);"
  452. + "var req = doRequest(options, function(response) {"
  453. + "response.on('data', function(chunk) {"
  454. + " var data = Buffer.from(chunk);"
  455. + " responseText += data.toString('utf8');"
  456. + " responseData = Buffer.concat([responseData, data]);"
  457. + "});"
  458. + "response.on('end', function() {"
  459. + "fs.writeFileSync('" + contentFile + "', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText, data: responseData.toString('base64')}}), 'utf8');"
  460. + "fs.unlinkSync('" + syncFile + "');"
  461. + "});"
  462. + "response.on('error', function(error) {"
  463. + "fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');"
  464. + "fs.unlinkSync('" + syncFile + "');"
  465. + "});"
  466. + "}).on('error', function(error) {"
  467. + "fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');"
  468. + "fs.unlinkSync('" + syncFile + "');"
  469. + "});"
  470. + (data ? "req.write('" + JSON.stringify(data).slice(1,-1).replace(/'/g, "\\'") + "');":"")
  471. + "req.end();";
  472. // Start the other Node Process, executing this string
  473. var syncProc = spawn(process.argv[0], ["-e", execString]);
  474. var statusText;
  475. while(fs.existsSync(syncFile)) {
  476. // Wait while the sync file is empty
  477. }
  478. self.responseText = fs.readFileSync(contentFile, 'utf8');
  479. // Kill the child process once the file has data
  480. syncProc.stdin.end();
  481. // Remove the temporary file
  482. fs.unlinkSync(contentFile);
  483. if (self.responseText.match(/^NODE-XMLHTTPREQUEST-ERROR:/)) {
  484. // If the file returned an error, handle it
  485. var errorObj = JSON.parse(self.responseText.replace(/^NODE-XMLHTTPREQUEST-ERROR:/, ""));
  486. self.handleError(errorObj, 503);
  487. } else {
  488. // If the file returned okay, parse its data and move to the DONE state
  489. self.status = self.responseText.replace(/^NODE-XMLHTTPREQUEST-STATUS:([0-9]*),.*/, "$1");
  490. var resp = JSON.parse(self.responseText.replace(/^NODE-XMLHTTPREQUEST-STATUS:[0-9]*,(.*)/, "$1"));
  491. response = {
  492. statusCode: self.status,
  493. headers: resp.data.headers
  494. };
  495. self.responseText = resp.data.text;
  496. self.response = Buffer.from(resp.data.data, 'base64');
  497. setState(self.DONE, true);
  498. }
  499. }
  500. };
  501. /**
  502. * Called when an error is encountered to deal with it.
  503. * @param status {number} HTTP status code to use rather than the default (0) for XHR errors.
  504. */
  505. this.handleError = function(error, status) {
  506. this.status = status || 0;
  507. this.statusText = error;
  508. this.responseText = error.stack;
  509. errorFlag = true;
  510. setState(this.DONE);
  511. };
  512. /**
  513. * Aborts a request.
  514. */
  515. this.abort = function() {
  516. if (request) {
  517. request.abort();
  518. request = null;
  519. }
  520. headers = Object.assign({}, defaultHeaders);
  521. this.responseText = "";
  522. this.responseXML = "";
  523. this.response = Buffer.alloc(0);
  524. errorFlag = abortedFlag = true
  525. if (this.readyState !== this.UNSENT
  526. && (this.readyState !== this.OPENED || sendFlag)
  527. && this.readyState !== this.DONE) {
  528. sendFlag = false;
  529. setState(this.DONE);
  530. }
  531. this.readyState = this.UNSENT;
  532. };
  533. /**
  534. * Adds an event listener. Preferred method of binding to events.
  535. */
  536. this.addEventListener = function(event, callback) {
  537. if (!(event in listeners)) {
  538. listeners[event] = [];
  539. }
  540. // Currently allows duplicate callbacks. Should it?
  541. listeners[event].push(callback);
  542. };
  543. /**
  544. * Remove an event callback that has already been bound.
  545. * Only works on the matching funciton, cannot be a copy.
  546. */
  547. this.removeEventListener = function(event, callback) {
  548. if (event in listeners) {
  549. // Filter will return a new array with the callback removed
  550. listeners[event] = listeners[event].filter(function(ev) {
  551. return ev !== callback;
  552. });
  553. }
  554. };
  555. /**
  556. * Dispatch any events, including both "on" methods and events attached using addEventListener.
  557. */
  558. this.dispatchEvent = function (event) {
  559. if (typeof self["on" + event] === "function") {
  560. if (this.readyState === this.DONE && settings.async)
  561. setTimeout(function() { self["on" + event]() }, 0)
  562. else
  563. self["on" + event]()
  564. }
  565. if (event in listeners) {
  566. for (let i = 0, len = listeners[event].length; i < len; i++) {
  567. if (this.readyState === this.DONE)
  568. setTimeout(function() { listeners[event][i].call(self) }, 0)
  569. else
  570. listeners[event][i].call(self)
  571. }
  572. }
  573. };
  574. /**
  575. * Changes readyState and calls onreadystatechange.
  576. *
  577. * @param int state New state
  578. */
  579. var setState = function(state) {
  580. if ((self.readyState === state) || (self.readyState === self.UNSENT && abortedFlag))
  581. return
  582. self.readyState = state;
  583. if (settings.async || self.readyState < self.OPENED || self.readyState === self.DONE) {
  584. self.dispatchEvent("readystatechange");
  585. }
  586. if (self.readyState === self.DONE) {
  587. let fire
  588. if (abortedFlag)
  589. fire = "abort"
  590. else if (errorFlag)
  591. fire = "error"
  592. else
  593. fire = "load"
  594. self.dispatchEvent(fire)
  595. // @TODO figure out InspectorInstrumentation::didLoadXHR(cookie)
  596. self.dispatchEvent("loadend");
  597. }
  598. };
  599. };