index.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  1. "use strict";
  2. var TextEncoder = require("@sinonjs/text-encoding").TextEncoder;
  3. var configureLogError = require("../configure-logger");
  4. var sinonEvent = require("../event");
  5. var extend = require("just-extend");
  6. function getWorkingXHR(globalScope) {
  7. var supportsXHR = typeof globalScope.XMLHttpRequest !== "undefined";
  8. if (supportsXHR) {
  9. return globalScope.XMLHttpRequest;
  10. }
  11. var supportsActiveX = typeof globalScope.ActiveXObject !== "undefined";
  12. if (supportsActiveX) {
  13. return function () {
  14. return new globalScope.ActiveXObject("MSXML2.XMLHTTP.3.0");
  15. };
  16. }
  17. return false;
  18. }
  19. var supportsProgress = typeof ProgressEvent !== "undefined";
  20. var supportsCustomEvent = typeof CustomEvent !== "undefined";
  21. var supportsFormData = typeof FormData !== "undefined";
  22. var supportsArrayBuffer = typeof ArrayBuffer !== "undefined";
  23. var supportsBlob = require("./blob").isSupported;
  24. var isReactNative = global.navigator && global.navigator.product === "ReactNative";
  25. var sinonXhr = { XMLHttpRequest: global.XMLHttpRequest };
  26. sinonXhr.GlobalXMLHttpRequest = global.XMLHttpRequest;
  27. sinonXhr.GlobalActiveXObject = global.ActiveXObject;
  28. sinonXhr.supportsActiveX = typeof sinonXhr.GlobalActiveXObject !== "undefined";
  29. sinonXhr.supportsXHR = typeof sinonXhr.GlobalXMLHttpRequest !== "undefined";
  30. sinonXhr.workingXHR = getWorkingXHR(global);
  31. sinonXhr.supportsTimeout =
  32. (sinonXhr.supportsXHR && "timeout" in (new sinonXhr.GlobalXMLHttpRequest()));
  33. sinonXhr.supportsCORS = isReactNative ||
  34. (sinonXhr.supportsXHR && "withCredentials" in (new sinonXhr.GlobalXMLHttpRequest()));
  35. // Ref: https://fetch.spec.whatwg.org/#forbidden-header-name
  36. var unsafeHeaders = {
  37. "Accept-Charset": true,
  38. "Access-Control-Request-Headers": true,
  39. "Access-Control-Request-Method": true,
  40. "Accept-Encoding": true,
  41. "Connection": true,
  42. "Content-Length": true,
  43. "Cookie": true,
  44. "Cookie2": true,
  45. "Content-Transfer-Encoding": true,
  46. "Date": true,
  47. "DNT": true,
  48. "Expect": true,
  49. "Host": true,
  50. "Keep-Alive": true,
  51. "Origin": true,
  52. "Referer": true,
  53. "TE": true,
  54. "Trailer": true,
  55. "Transfer-Encoding": true,
  56. "Upgrade": true,
  57. "User-Agent": true,
  58. "Via": true
  59. };
  60. function EventTargetHandler() {
  61. var self = this;
  62. var events = ["loadstart", "progress", "abort", "error", "load", "timeout", "loadend"];
  63. function addEventListener(eventName) {
  64. self.addEventListener(eventName, function (event) {
  65. var listener = self["on" + eventName];
  66. if (listener && typeof listener === "function") {
  67. listener.call(this, event);
  68. }
  69. });
  70. }
  71. events.forEach(addEventListener);
  72. }
  73. EventTargetHandler.prototype = sinonEvent.EventTarget;
  74. // Note that for FakeXMLHttpRequest to work pre ES5
  75. // we lose some of the alignment with the spec.
  76. // To ensure as close a match as possible,
  77. // set responseType before calling open, send or respond;
  78. function FakeXMLHttpRequest(config) {
  79. EventTargetHandler.call(this);
  80. this.readyState = FakeXMLHttpRequest.UNSENT;
  81. this.requestHeaders = {};
  82. this.requestBody = null;
  83. this.status = 0;
  84. this.statusText = "";
  85. this.upload = new EventTargetHandler();
  86. this.responseType = "";
  87. this.response = "";
  88. this.logError = configureLogError(config);
  89. if (sinonXhr.supportsTimeout) {
  90. this.timeout = 0;
  91. }
  92. if (sinonXhr.supportsCORS) {
  93. this.withCredentials = false;
  94. }
  95. if (typeof FakeXMLHttpRequest.onCreate === "function") {
  96. FakeXMLHttpRequest.onCreate(this);
  97. }
  98. }
  99. function verifyState(xhr) {
  100. if (xhr.readyState !== FakeXMLHttpRequest.OPENED) {
  101. throw new Error("INVALID_STATE_ERR");
  102. }
  103. if (xhr.sendFlag) {
  104. throw new Error("INVALID_STATE_ERR");
  105. }
  106. }
  107. function normalizeHeaderValue(value) {
  108. // Ref: https://fetch.spec.whatwg.org/#http-whitespace-bytes
  109. /*eslint no-control-regex: "off"*/
  110. return value.replace(/^[\x09\x0A\x0D\x20]+|[\x09\x0A\x0D\x20]+$/g, "");
  111. }
  112. function getHeader(headers, header) {
  113. var foundHeader = Object.keys(headers).filter(function (h) {
  114. return h.toLowerCase() === header.toLowerCase();
  115. });
  116. return foundHeader[0] || null;
  117. }
  118. function excludeSetCookie2Header(header) {
  119. return !/^Set-Cookie2?$/i.test(header);
  120. }
  121. // largest arity in XHR is 5 - XHR#open
  122. var apply = function (obj, method, args) {
  123. switch (args.length) {
  124. case 0: return obj[method]();
  125. case 1: return obj[method](args[0]);
  126. case 2: return obj[method](args[0], args[1]);
  127. case 3: return obj[method](args[0], args[1], args[2]);
  128. case 4: return obj[method](args[0], args[1], args[2], args[3]);
  129. case 5: return obj[method](args[0], args[1], args[2], args[3], args[4]);
  130. default: throw new Error("Unhandled case");
  131. }
  132. };
  133. FakeXMLHttpRequest.filters = [];
  134. FakeXMLHttpRequest.addFilter = function addFilter(fn) {
  135. this.filters.push(fn);
  136. };
  137. FakeXMLHttpRequest.defake = function defake(fakeXhr, xhrArgs) {
  138. var xhr = new sinonXhr.workingXHR(); // eslint-disable-line new-cap
  139. [
  140. "open",
  141. "setRequestHeader",
  142. "abort",
  143. "getResponseHeader",
  144. "getAllResponseHeaders",
  145. "addEventListener",
  146. "overrideMimeType",
  147. "removeEventListener"
  148. ].forEach(function (method) {
  149. fakeXhr[method] = function () {
  150. return apply(xhr, method, arguments);
  151. };
  152. });
  153. fakeXhr.send = function () {
  154. // Ref: https://xhr.spec.whatwg.org/#the-responsetype-attribute
  155. if (xhr.responseType !== fakeXhr.responseType) {
  156. xhr.responseType = fakeXhr.responseType;
  157. }
  158. return apply(xhr, "send", arguments);
  159. };
  160. var copyAttrs = function (args) {
  161. args.forEach(function (attr) {
  162. fakeXhr[attr] = xhr[attr];
  163. });
  164. };
  165. var stateChangeStart = function () {
  166. fakeXhr.readyState = xhr.readyState;
  167. if (xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) {
  168. copyAttrs(["status", "statusText"]);
  169. }
  170. if (xhr.readyState >= FakeXMLHttpRequest.LOADING) {
  171. copyAttrs(["response"]);
  172. if (xhr.responseType === "" || xhr.responseType === "text") {
  173. copyAttrs(["responseText"]);
  174. }
  175. }
  176. if (
  177. xhr.readyState === FakeXMLHttpRequest.DONE &&
  178. (xhr.responseType === "" || xhr.responseType === "document")
  179. ) {
  180. copyAttrs(["responseXML"]);
  181. }
  182. };
  183. var stateChangeEnd = function () {
  184. if (fakeXhr.onreadystatechange) {
  185. fakeXhr.onreadystatechange.call(fakeXhr, { target: fakeXhr, currentTarget: fakeXhr });
  186. }
  187. };
  188. var stateChange = function stateChange() {
  189. stateChangeStart();
  190. stateChangeEnd();
  191. };
  192. if (xhr.addEventListener) {
  193. xhr.addEventListener("readystatechange", stateChangeStart);
  194. Object.keys(fakeXhr.eventListeners).forEach(function (event) {
  195. /*eslint-disable no-loop-func*/
  196. fakeXhr.eventListeners[event].forEach(function (handler) {
  197. xhr.addEventListener(event, handler.listener, {
  198. capture: handler.capture,
  199. once: handler.once
  200. });
  201. });
  202. /*eslint-enable no-loop-func*/
  203. });
  204. xhr.addEventListener("readystatechange", stateChangeEnd);
  205. } else {
  206. xhr.onreadystatechange = stateChange;
  207. }
  208. apply(xhr, "open", xhrArgs);
  209. };
  210. FakeXMLHttpRequest.useFilters = false;
  211. function verifyRequestOpened(xhr) {
  212. if (xhr.readyState !== FakeXMLHttpRequest.OPENED) {
  213. throw new Error("INVALID_STATE_ERR - " + xhr.readyState);
  214. }
  215. }
  216. function verifyRequestSent(xhr) {
  217. if (xhr.readyState === FakeXMLHttpRequest.DONE) {
  218. throw new Error("Request done");
  219. }
  220. }
  221. function verifyHeadersReceived(xhr) {
  222. if (xhr.async && xhr.readyState !== FakeXMLHttpRequest.HEADERS_RECEIVED) {
  223. throw new Error("No headers received");
  224. }
  225. }
  226. function verifyResponseBodyType(body, responseType) {
  227. var error = null;
  228. var isString = typeof body === "string";
  229. if (responseType === "arraybuffer") {
  230. if (!isString && !(body instanceof ArrayBuffer)) {
  231. error = new Error("Attempted to respond to fake XMLHttpRequest with " +
  232. body + ", which is not a string or ArrayBuffer.");
  233. error.name = "InvalidBodyException";
  234. }
  235. }
  236. else if (!isString) {
  237. error = new Error("Attempted to respond to fake XMLHttpRequest with " +
  238. body + ", which is not a string.");
  239. error.name = "InvalidBodyException";
  240. }
  241. if (error) {
  242. throw error;
  243. }
  244. }
  245. function convertToArrayBuffer(body, encoding) {
  246. if (body instanceof ArrayBuffer) {
  247. return body;
  248. }
  249. return new TextEncoder(encoding || "utf-8").encode(body).buffer;
  250. }
  251. function isXmlContentType(contentType) {
  252. return !contentType || /(text\/xml)|(application\/xml)|(\+xml)/.test(contentType);
  253. }
  254. function convertResponseBody(responseType, contentType, body) {
  255. if (responseType === "" || responseType === "text") {
  256. return body;
  257. } else if (supportsArrayBuffer && responseType === "arraybuffer") {
  258. return convertToArrayBuffer(body);
  259. } else if (responseType === "json") {
  260. try {
  261. return JSON.parse(body);
  262. } catch (e) {
  263. // Return parsing failure as null
  264. return null;
  265. }
  266. } else if (supportsBlob && responseType === "blob") {
  267. var blobOptions = {};
  268. if (contentType) {
  269. blobOptions.type = contentType;
  270. }
  271. return new Blob([convertToArrayBuffer(body)], blobOptions);
  272. } else if (responseType === "document") {
  273. if (isXmlContentType(contentType)) {
  274. return FakeXMLHttpRequest.parseXML(body);
  275. }
  276. return null;
  277. }
  278. throw new Error("Invalid responseType " + responseType);
  279. }
  280. function clearResponse(xhr) {
  281. if (xhr.responseType === "" || xhr.responseType === "text") {
  282. xhr.response = xhr.responseText = "";
  283. } else {
  284. xhr.response = xhr.responseText = null;
  285. }
  286. xhr.responseXML = null;
  287. }
  288. /**
  289. * Steps to follow when there is an error, according to:
  290. * https://xhr.spec.whatwg.org/#request-error-steps
  291. */
  292. function requestErrorSteps(xhr) {
  293. clearResponse(xhr);
  294. xhr.errorFlag = true;
  295. xhr.requestHeaders = {};
  296. xhr.responseHeaders = {};
  297. if (xhr.readyState !== FakeXMLHttpRequest.UNSENT && xhr.sendFlag
  298. && xhr.readyState !== FakeXMLHttpRequest.DONE) {
  299. xhr.readyStateChange(FakeXMLHttpRequest.DONE);
  300. xhr.sendFlag = false;
  301. }
  302. }
  303. FakeXMLHttpRequest.parseXML = function parseXML(text) {
  304. // Treat empty string as parsing failure
  305. if (text !== "") {
  306. try {
  307. if (typeof DOMParser !== "undefined") {
  308. var parser = new DOMParser();
  309. var parsererrorNS = "";
  310. try {
  311. var parsererrors = parser
  312. .parseFromString("INVALID", "text/xml")
  313. .getElementsByTagName("parsererror");
  314. if (parsererrors.length) {
  315. parsererrorNS = parsererrors[0].namespaceURI;
  316. }
  317. } catch (e) {
  318. // passing invalid XML makes IE11 throw
  319. // so no namespace needs to be determined
  320. }
  321. var result;
  322. try {
  323. result = parser.parseFromString(text, "text/xml");
  324. } catch (err) {
  325. return null;
  326. }
  327. return result.getElementsByTagNameNS(parsererrorNS, "parsererror").length
  328. ? null : result;
  329. }
  330. var xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
  331. xmlDoc.async = "false";
  332. xmlDoc.loadXML(text);
  333. return xmlDoc.parseError.errorCode !== 0
  334. ? null : xmlDoc;
  335. } catch (e) {
  336. // Unable to parse XML - no biggie
  337. }
  338. }
  339. return null;
  340. };
  341. FakeXMLHttpRequest.statusCodes = {
  342. 100: "Continue",
  343. 101: "Switching Protocols",
  344. 200: "OK",
  345. 201: "Created",
  346. 202: "Accepted",
  347. 203: "Non-Authoritative Information",
  348. 204: "No Content",
  349. 205: "Reset Content",
  350. 206: "Partial Content",
  351. 207: "Multi-Status",
  352. 300: "Multiple Choice",
  353. 301: "Moved Permanently",
  354. 302: "Found",
  355. 303: "See Other",
  356. 304: "Not Modified",
  357. 305: "Use Proxy",
  358. 307: "Temporary Redirect",
  359. 400: "Bad Request",
  360. 401: "Unauthorized",
  361. 402: "Payment Required",
  362. 403: "Forbidden",
  363. 404: "Not Found",
  364. 405: "Method Not Allowed",
  365. 406: "Not Acceptable",
  366. 407: "Proxy Authentication Required",
  367. 408: "Request Timeout",
  368. 409: "Conflict",
  369. 410: "Gone",
  370. 411: "Length Required",
  371. 412: "Precondition Failed",
  372. 413: "Request Entity Too Large",
  373. 414: "Request-URI Too Long",
  374. 415: "Unsupported Media Type",
  375. 416: "Requested Range Not Satisfiable",
  376. 417: "Expectation Failed",
  377. 422: "Unprocessable Entity",
  378. 500: "Internal Server Error",
  379. 501: "Not Implemented",
  380. 502: "Bad Gateway",
  381. 503: "Service Unavailable",
  382. 504: "Gateway Timeout",
  383. 505: "HTTP Version Not Supported"
  384. };
  385. extend(FakeXMLHttpRequest.prototype, sinonEvent.EventTarget, {
  386. async: true,
  387. open: function open(method, url, async, username, password) {
  388. this.method = method;
  389. this.url = url;
  390. this.async = typeof async === "boolean" ? async : true;
  391. this.username = username;
  392. this.password = password;
  393. clearResponse(this);
  394. this.requestHeaders = {};
  395. this.sendFlag = false;
  396. if (FakeXMLHttpRequest.useFilters === true) {
  397. var xhrArgs = arguments;
  398. var defake = FakeXMLHttpRequest.filters.some(function (filter) {
  399. return filter.apply(this, xhrArgs);
  400. });
  401. if (defake) {
  402. FakeXMLHttpRequest.defake(this, arguments);
  403. return;
  404. }
  405. }
  406. this.readyStateChange(FakeXMLHttpRequest.OPENED);
  407. },
  408. readyStateChange: function readyStateChange(state) {
  409. this.readyState = state;
  410. var readyStateChangeEvent = new sinonEvent.Event("readystatechange", false, false, this);
  411. var event, progress;
  412. if (typeof this.onreadystatechange === "function") {
  413. try {
  414. this.onreadystatechange(readyStateChangeEvent);
  415. } catch (e) {
  416. this.logError("Fake XHR onreadystatechange handler", e);
  417. }
  418. }
  419. if (this.readyState === FakeXMLHttpRequest.DONE) {
  420. if (this.timedOut || this.aborted || this.status === 0) {
  421. progress = {loaded: 0, total: 0};
  422. event = (this.timedOut && "timeout") || (this.aborted && "abort") || "error";
  423. } else {
  424. progress = {loaded: 100, total: 100};
  425. event = "load";
  426. }
  427. if (supportsProgress) {
  428. this.upload.dispatchEvent(new sinonEvent.ProgressEvent("progress", progress, this));
  429. this.upload.dispatchEvent(new sinonEvent.ProgressEvent(event, progress, this));
  430. this.upload.dispatchEvent(new sinonEvent.ProgressEvent("loadend", progress, this));
  431. }
  432. this.dispatchEvent(new sinonEvent.ProgressEvent("progress", progress, this));
  433. this.dispatchEvent(new sinonEvent.ProgressEvent(event, progress, this));
  434. this.dispatchEvent(new sinonEvent.ProgressEvent("loadend", progress, this));
  435. }
  436. this.dispatchEvent(readyStateChangeEvent);
  437. },
  438. // Ref https://xhr.spec.whatwg.org/#the-setrequestheader()-method
  439. setRequestHeader: function setRequestHeader(header, value) {
  440. if (typeof value !== "string") {
  441. throw new TypeError("By RFC7230, section 3.2.4, header values should be strings. Got " + typeof value);
  442. }
  443. verifyState(this);
  444. var checkUnsafeHeaders = true;
  445. if (typeof this.unsafeHeadersEnabled === "function") {
  446. checkUnsafeHeaders = this.unsafeHeadersEnabled();
  447. }
  448. if (checkUnsafeHeaders && (getHeader(unsafeHeaders, header) !== null || /^(Sec-|Proxy-)/i.test(header))) {
  449. throw new Error("Refused to set unsafe header \"" + header + "\"");
  450. }
  451. value = normalizeHeaderValue(value);
  452. var existingHeader = getHeader(this.requestHeaders, header);
  453. if (existingHeader) {
  454. this.requestHeaders[existingHeader] += ", " + value;
  455. } else {
  456. this.requestHeaders[header] = value;
  457. }
  458. },
  459. setStatus: function setStatus(status) {
  460. var sanitizedStatus = typeof status === "number" ? status : 200;
  461. verifyRequestOpened(this);
  462. this.status = sanitizedStatus;
  463. this.statusText = FakeXMLHttpRequest.statusCodes[sanitizedStatus];
  464. },
  465. // Helps testing
  466. setResponseHeaders: function setResponseHeaders(headers) {
  467. verifyRequestOpened(this);
  468. var responseHeaders = this.responseHeaders = {};
  469. Object.keys(headers).forEach(function (header) {
  470. responseHeaders[header] = headers[header];
  471. });
  472. if (this.async) {
  473. this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED);
  474. } else {
  475. this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED;
  476. }
  477. },
  478. // Currently treats ALL data as a DOMString (i.e. no Document)
  479. send: function send(data) {
  480. verifyState(this);
  481. if (!/^(head)$/i.test(this.method)) {
  482. var contentType = getHeader(this.requestHeaders, "Content-Type");
  483. if (this.requestHeaders[contentType]) {
  484. var value = this.requestHeaders[contentType].split(";");
  485. this.requestHeaders[contentType] = value[0] + ";charset=utf-8";
  486. } else if (supportsFormData && !(data instanceof FormData)) {
  487. this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
  488. }
  489. this.requestBody = data;
  490. }
  491. this.errorFlag = false;
  492. this.sendFlag = this.async;
  493. clearResponse(this);
  494. this.readyStateChange(FakeXMLHttpRequest.OPENED);
  495. if (typeof this.onSend === "function") {
  496. this.onSend(this);
  497. }
  498. // Only listen if setInterval and Date are a stubbed.
  499. if (sinonXhr.supportsTimeout && typeof setInterval.clock === "object" && typeof Date.clock === "object") {
  500. var initiatedTime = Date.now();
  501. var self = this;
  502. // Listen to any possible tick by fake timers and check to see if timeout has
  503. // been exceeded. It's important to note that timeout can be changed while a request
  504. // is in flight, so we must check anytime the end user forces a clock tick to make
  505. // sure timeout hasn't changed.
  506. // https://xhr.spec.whatwg.org/#dfnReturnLink-2
  507. var clearIntervalId = setInterval(function () {
  508. // Check if the readyState has been reset or is done. If this is the case, there
  509. // should be no timeout. This will also prevent aborted requests and
  510. // fakeServerWithClock from triggering unnecessary responses.
  511. if (self.readyState === FakeXMLHttpRequest.UNSENT
  512. || self.readyState === FakeXMLHttpRequest.DONE) {
  513. clearInterval(clearIntervalId);
  514. } else if (typeof self.timeout === "number" && self.timeout > 0) {
  515. if (Date.now() >= (initiatedTime + self.timeout)) {
  516. self.triggerTimeout();
  517. clearInterval(clearIntervalId);
  518. }
  519. }
  520. }, 1);
  521. }
  522. this.dispatchEvent(new sinonEvent.Event("loadstart", false, false, this));
  523. },
  524. abort: function abort() {
  525. this.aborted = true;
  526. requestErrorSteps(this);
  527. this.readyState = FakeXMLHttpRequest.UNSENT;
  528. },
  529. error: function () {
  530. clearResponse(this);
  531. this.errorFlag = true;
  532. this.requestHeaders = {};
  533. this.responseHeaders = {};
  534. this.readyStateChange(FakeXMLHttpRequest.DONE);
  535. },
  536. triggerTimeout: function triggerTimeout() {
  537. if (sinonXhr.supportsTimeout) {
  538. this.timedOut = true;
  539. requestErrorSteps(this);
  540. }
  541. },
  542. getResponseHeader: function getResponseHeader(header) {
  543. if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
  544. return null;
  545. }
  546. if (/^Set-Cookie2?$/i.test(header)) {
  547. return null;
  548. }
  549. header = getHeader(this.responseHeaders, header);
  550. return this.responseHeaders[header] || null;
  551. },
  552. getAllResponseHeaders: function getAllResponseHeaders() {
  553. if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
  554. return "";
  555. }
  556. var responseHeaders = this.responseHeaders;
  557. var headers = Object.keys(responseHeaders)
  558. .filter(excludeSetCookie2Header)
  559. .reduce(function (prev, header) {
  560. var value = responseHeaders[header];
  561. return prev + (header + ": " + value + "\r\n");
  562. }, "");
  563. return headers;
  564. },
  565. setResponseBody: function setResponseBody(body) {
  566. verifyRequestSent(this);
  567. verifyHeadersReceived(this);
  568. verifyResponseBodyType(body, this.responseType);
  569. var contentType = this.overriddenMimeType || this.getResponseHeader("Content-Type");
  570. var isTextResponse = this.responseType === "" || this.responseType === "text";
  571. clearResponse(this);
  572. if (this.async) {
  573. var chunkSize = this.chunkSize || 10;
  574. var index = 0;
  575. do {
  576. this.readyStateChange(FakeXMLHttpRequest.LOADING);
  577. if (isTextResponse) {
  578. this.responseText = this.response += body.substring(index, index + chunkSize);
  579. }
  580. index += chunkSize;
  581. } while (index < body.length);
  582. }
  583. this.response = convertResponseBody(this.responseType, contentType, body);
  584. if (isTextResponse) {
  585. this.responseText = this.response;
  586. }
  587. if (this.responseType === "document") {
  588. this.responseXML = this.response;
  589. } else if (this.responseType === "" && isXmlContentType(contentType)) {
  590. this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText);
  591. }
  592. this.readyStateChange(FakeXMLHttpRequest.DONE);
  593. },
  594. respond: function respond(status, headers, body) {
  595. this.setStatus(status);
  596. this.setResponseHeaders(headers || {});
  597. this.setResponseBody(body || "");
  598. },
  599. uploadProgress: function uploadProgress(progressEventRaw) {
  600. if (supportsProgress) {
  601. this.upload.dispatchEvent(new sinonEvent.ProgressEvent("progress", progressEventRaw, this.upload));
  602. }
  603. },
  604. downloadProgress: function downloadProgress(progressEventRaw) {
  605. if (supportsProgress) {
  606. this.dispatchEvent(new sinonEvent.ProgressEvent("progress", progressEventRaw, this));
  607. }
  608. },
  609. uploadError: function uploadError(error) {
  610. if (supportsCustomEvent) {
  611. this.upload.dispatchEvent(new sinonEvent.CustomEvent("error", {detail: error}));
  612. }
  613. },
  614. overrideMimeType: function overrideMimeType(type) {
  615. if (this.readyState >= FakeXMLHttpRequest.LOADING) {
  616. throw new Error("INVALID_STATE_ERR");
  617. }
  618. this.overriddenMimeType = type;
  619. }
  620. });
  621. var states = {
  622. UNSENT: 0,
  623. OPENED: 1,
  624. HEADERS_RECEIVED: 2,
  625. LOADING: 3,
  626. DONE: 4
  627. };
  628. extend(FakeXMLHttpRequest, states);
  629. extend(FakeXMLHttpRequest.prototype, states);
  630. function useFakeXMLHttpRequest() {
  631. FakeXMLHttpRequest.restore = function restore(keepOnCreate) {
  632. if (sinonXhr.supportsXHR) {
  633. global.XMLHttpRequest = sinonXhr.GlobalXMLHttpRequest;
  634. }
  635. if (sinonXhr.supportsActiveX) {
  636. global.ActiveXObject = sinonXhr.GlobalActiveXObject;
  637. }
  638. delete FakeXMLHttpRequest.restore;
  639. if (keepOnCreate !== true) {
  640. delete FakeXMLHttpRequest.onCreate;
  641. }
  642. };
  643. if (sinonXhr.supportsXHR) {
  644. global.XMLHttpRequest = FakeXMLHttpRequest;
  645. }
  646. if (sinonXhr.supportsActiveX) {
  647. global.ActiveXObject = function ActiveXObject(objId) {
  648. if (objId === "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/i.test(objId)) {
  649. return new FakeXMLHttpRequest();
  650. }
  651. return new sinonXhr.GlobalActiveXObject(objId);
  652. };
  653. }
  654. return FakeXMLHttpRequest;
  655. }
  656. module.exports = {
  657. xhr: sinonXhr,
  658. FakeXMLHttpRequest: FakeXMLHttpRequest,
  659. useFakeXMLHttpRequest: useFakeXMLHttpRequest
  660. };