1244 lines
43 KiB
JavaScript
Executable file
1244 lines
43 KiB
JavaScript
Executable file
// Generated by CoffeeScript 2.5.1
|
|
(function() {
|
|
// This file's name is set up in such a way that it will always show up first in
|
|
// the list of files given to coffee --join, so that the other files can assume
|
|
// that XMLHttpRequestEventTarget was already defined.
|
|
|
|
// The DOM EventTarget subclass used by XMLHttpRequest.
|
|
|
|
// @see http://xhr.spec.whatwg.org/#interface-xmlhttprequest
|
|
var InvalidStateError, NetworkError, ProgressEvent, SecurityError, SyntaxError, XMLHttpRequest, XMLHttpRequestEventTarget, XMLHttpRequestUpload, http, https, os, url;
|
|
|
|
XMLHttpRequestEventTarget = (function() {
|
|
class XMLHttpRequestEventTarget {
|
|
// @private
|
|
// This is an abstract class and should not be instantiated directly.
|
|
constructor() {
|
|
this.onloadstart = null;
|
|
this.onprogress = null;
|
|
this.onabort = null;
|
|
this.onerror = null;
|
|
this.onload = null;
|
|
this.ontimeout = null;
|
|
this.onloadend = null;
|
|
this._listeners = {};
|
|
}
|
|
|
|
// Adds a new-style listener for one of the XHR events.
|
|
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#events
|
|
|
|
// @param {String} eventType an XHR event type, such as 'readystatechange'
|
|
// @param {function(ProgressEvent)} listener function that will be called when
|
|
// the event fires
|
|
// @return {undefined} undefined
|
|
addEventListener(eventType, listener) {
|
|
var base;
|
|
eventType = eventType.toLowerCase();
|
|
(base = this._listeners)[eventType] || (base[eventType] = []);
|
|
this._listeners[eventType].push(listener);
|
|
return void 0;
|
|
}
|
|
|
|
// Removes an event listener added by calling addEventListener.
|
|
|
|
// @param {String} eventType an XHR event type, such as 'readystatechange'
|
|
// @param {function(ProgressEvent)} listener the value passed in a previous
|
|
// call to addEventListener.
|
|
// @return {undefined} undefined
|
|
removeEventListener(eventType, listener) {
|
|
var index;
|
|
eventType = eventType.toLowerCase();
|
|
if (this._listeners[eventType]) {
|
|
index = this._listeners[eventType].indexOf(listener);
|
|
if (index !== -1) {
|
|
this._listeners[eventType].splice(index, 1);
|
|
}
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
// Calls all the listeners for an event.
|
|
|
|
// @param {ProgressEvent} event the event to be dispatched
|
|
// @return {undefined} undefined
|
|
dispatchEvent(event) {
|
|
var eventType, j, len, listener, listeners;
|
|
event.currentTarget = event.target = this;
|
|
eventType = event.type;
|
|
if (listeners = this._listeners[eventType]) {
|
|
for (j = 0, len = listeners.length; j < len; j++) {
|
|
listener = listeners[j];
|
|
listener.call(this, event);
|
|
}
|
|
}
|
|
if (listener = this[`on${eventType}`]) {
|
|
listener.call(this, event);
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
};
|
|
|
|
// @property {function(ProgressEvent)} DOM level 0-style handler
|
|
// for the 'loadstart' event
|
|
XMLHttpRequestEventTarget.prototype.onloadstart = null;
|
|
|
|
// @property {function(ProgressEvent)} DOM level 0-style handler
|
|
// for the 'progress' event
|
|
XMLHttpRequestEventTarget.prototype.onprogress = null;
|
|
|
|
// @property {function(ProgressEvent)} DOM level 0-style handler
|
|
// for the 'abort' event
|
|
XMLHttpRequestEventTarget.prototype.onabort = null;
|
|
|
|
// @property {function(ProgressEvent)} DOM level 0-style handler
|
|
// for the 'error' event
|
|
XMLHttpRequestEventTarget.prototype.onerror = null;
|
|
|
|
// @property {function(ProgressEvent)} DOM level 0-style handler
|
|
// for the 'load' event
|
|
XMLHttpRequestEventTarget.prototype.onload = null;
|
|
|
|
// @property {function(ProgressEvent)} DOM level 0-style handler
|
|
// for the 'timeout' event
|
|
XMLHttpRequestEventTarget.prototype.ontimeout = null;
|
|
|
|
// @property {function(ProgressEvent)} DOM level 0-style handler
|
|
// for the 'loadend' event
|
|
XMLHttpRequestEventTarget.prototype.onloadend = null;
|
|
|
|
return XMLHttpRequestEventTarget;
|
|
|
|
}).call(this);
|
|
|
|
// This file's name is set up in such a way that it will always show up second
|
|
// in the list of files given to coffee --join, so it can use the
|
|
// XMLHttpRequestEventTarget definition and so that the other files can assume
|
|
// that XMLHttpRequest was already defined.
|
|
http = require('http');
|
|
|
|
https = require('https');
|
|
|
|
os = require('os');
|
|
|
|
url = require('url');
|
|
|
|
XMLHttpRequest = (function() {
|
|
// The ECMAScript HTTP API.
|
|
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#introduction
|
|
class XMLHttpRequest extends XMLHttpRequestEventTarget {
|
|
// Creates a new request.
|
|
|
|
// @param {Object} options one or more of the options below
|
|
// @option options {Boolean} anon if true, the request's anonymous flag
|
|
// will be set
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#constructors
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#anonymous-flag
|
|
constructor(options) {
|
|
super();
|
|
this.onreadystatechange = null;
|
|
this._anonymous = options && options.anon;
|
|
this.readyState = XMLHttpRequest.UNSENT;
|
|
this.response = null;
|
|
this.responseText = '';
|
|
this.responseType = '';
|
|
this.responseURL = '';
|
|
this.status = 0;
|
|
this.statusText = '';
|
|
this.timeout = 0;
|
|
this.upload = new XMLHttpRequestUpload(this);
|
|
this._method = null; // String
|
|
this._url = null; // Return value of url.parse()
|
|
this._sync = false;
|
|
this._headers = null; // Object<String, String>
|
|
this._loweredHeaders = null; // Object<lowercase String, String>
|
|
this._mimeOverride = null;
|
|
this._request = null; // http.ClientRequest
|
|
this._response = null; // http.ClientResponse
|
|
this._responseParts = null; // Array<Buffer, String>
|
|
this._responseHeaders = null; // Object<lowercase String, String>
|
|
this._aborting = null;
|
|
this._error = null;
|
|
this._loadedBytes = 0;
|
|
this._totalBytes = 0;
|
|
this._lengthComputable = false;
|
|
}
|
|
|
|
// Sets the XHR's method, URL, synchronous flag, and authentication params.
|
|
|
|
// @param {String} method the HTTP method to be used
|
|
// @param {String} url the URL that the request will be made to
|
|
// @param {?Boolean} async if false, the XHR should be processed
|
|
// synchronously; true by default
|
|
// @param {?String} user the user credential to be used in HTTP basic
|
|
// authentication
|
|
// @param {?String} password the password credential to be used in HTTP basic
|
|
// authentication
|
|
// @return {undefined} undefined
|
|
// @throw {SecurityError} method is not one of the allowed methods
|
|
// @throw {SyntaxError} urlString is not a valid URL
|
|
// @throw {Error} the URL contains an unsupported protocol; the supported
|
|
// protocols are file, http and https
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#the-open()-method
|
|
open(method, url, async, user, password) {
|
|
var xhrUrl;
|
|
method = method.toUpperCase();
|
|
if (method in this._restrictedMethods) {
|
|
throw new SecurityError(`HTTP method ${method} is not allowed in XHR`);
|
|
}
|
|
xhrUrl = this._parseUrl(url);
|
|
if (async === void 0) {
|
|
async = true;
|
|
}
|
|
switch (this.readyState) {
|
|
case XMLHttpRequest.UNSENT:
|
|
case XMLHttpRequest.OPENED:
|
|
case XMLHttpRequest.DONE:
|
|
// Nothing to do here.
|
|
null;
|
|
break;
|
|
case XMLHttpRequest.HEADERS_RECEIVED:
|
|
case XMLHttpRequest.LOADING:
|
|
// TODO(pwnall): terminate abort(), terminate send()
|
|
null;
|
|
}
|
|
this._method = method;
|
|
this._url = xhrUrl;
|
|
this._sync = !async;
|
|
this._headers = {};
|
|
this._loweredHeaders = {};
|
|
this._mimeOverride = null;
|
|
this._setReadyState(XMLHttpRequest.OPENED);
|
|
this._request = null;
|
|
this._response = null;
|
|
this.status = 0;
|
|
this.statusText = '';
|
|
this._responseParts = [];
|
|
this._responseHeaders = null;
|
|
this._loadedBytes = 0;
|
|
this._totalBytes = 0;
|
|
this._lengthComputable = false;
|
|
return void 0;
|
|
}
|
|
|
|
// Appends a header to the list of author request headers.
|
|
|
|
// @param {String} name the HTTP header name
|
|
// @param {String} value the HTTP header value
|
|
// @return {undefined} undefined
|
|
// @throw {InvalidStateError} readyState is not OPENED
|
|
// @throw {SyntaxError} name is not a valid HTTP header name or value is not
|
|
// a valid HTTP header value
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader()-method
|
|
setRequestHeader(name, value) {
|
|
var loweredName;
|
|
if (this.readyState !== XMLHttpRequest.OPENED) {
|
|
throw new InvalidStateError("XHR readyState must be OPENED");
|
|
}
|
|
loweredName = name.toLowerCase();
|
|
if (this._restrictedHeaders[loweredName] || /^sec\-/.test(loweredName) || /^proxy-/.test(loweredName)) {
|
|
console.warn(`Refused to set unsafe header \"${name}\"`);
|
|
return void 0;
|
|
}
|
|
value = value.toString();
|
|
if (loweredName in this._loweredHeaders) {
|
|
// Combine value with the existing header value.
|
|
name = this._loweredHeaders[loweredName];
|
|
this._headers[name] = this._headers[name] + ', ' + value;
|
|
} else {
|
|
// New header.
|
|
this._loweredHeaders[loweredName] = name;
|
|
this._headers[name] = value;
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
// Initiates the request.
|
|
|
|
// @param {?String, ?ArrayBufferView} data the data to be sent; ignored for
|
|
// GET and HEAD requests
|
|
// @return {undefined} undefined
|
|
// @throw {InvalidStateError} readyState is not OPENED
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#the-send()-method
|
|
send(data) {
|
|
if (this.readyState !== XMLHttpRequest.OPENED) {
|
|
throw new InvalidStateError("XHR readyState must be OPENED");
|
|
}
|
|
if (this._request) {
|
|
throw new InvalidStateError("send() already called");
|
|
}
|
|
switch (this._url.protocol) {
|
|
case 'file:':
|
|
this._sendFile(data);
|
|
break;
|
|
case 'http:':
|
|
case 'https:':
|
|
this._sendHttp(data);
|
|
break;
|
|
default:
|
|
throw new NetworkError(`Unsupported protocol ${this._url.protocol}`);
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
// Cancels the network activity performed by this request.
|
|
|
|
// @return {undefined} undefined
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#the-abort()-method
|
|
abort() {
|
|
if (!this._request) {
|
|
return;
|
|
}
|
|
this._request.abort();
|
|
this._setError();
|
|
this._dispatchProgress('abort');
|
|
this._dispatchProgress('loadend');
|
|
return void 0;
|
|
}
|
|
|
|
// Returns a header value in the HTTP response for this XHR.
|
|
|
|
// @param {String} name case-insensitive HTTP header name
|
|
// @return {?String} value the value of the header whose name matches the
|
|
// given name, or null if there is no such header
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method
|
|
getResponseHeader(name) {
|
|
var loweredName;
|
|
if (!this._responseHeaders) {
|
|
return null;
|
|
}
|
|
loweredName = name.toLowerCase();
|
|
if (loweredName in this._responseHeaders) {
|
|
return this._responseHeaders[loweredName];
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Returns all the HTTP headers in this XHR's response.
|
|
|
|
// @return {String} header lines separated by CR LF, where each header line
|
|
// has the name and value separated by a ": " (colon, space); the empty
|
|
// string is returned if the headers are not available
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders()-method
|
|
getAllResponseHeaders() {
|
|
var lines, name, value;
|
|
if (!this._responseHeaders) {
|
|
return '';
|
|
}
|
|
lines = (function() {
|
|
var ref, results;
|
|
ref = this._responseHeaders;
|
|
results = [];
|
|
for (name in ref) {
|
|
value = ref[name];
|
|
results.push(`${name}: ${value}`);
|
|
}
|
|
return results;
|
|
}).call(this);
|
|
return lines.join("\r\n");
|
|
}
|
|
|
|
// Overrides the Content-Type
|
|
|
|
// @return {undefined} undefined
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#the-overridemimetype()-method
|
|
overrideMimeType(newMimeType) {
|
|
if (this.readyState === XMLHttpRequest.LOADING || this.readyState === XMLHttpRequest.DONE) {
|
|
throw new InvalidStateError("overrideMimeType() not allowed in LOADING or DONE");
|
|
}
|
|
this._mimeOverride = newMimeType.toLowerCase();
|
|
return void 0;
|
|
}
|
|
|
|
// Network configuration not exposed in the XHR API.
|
|
|
|
// Although the XMLHttpRequest specification calls itself "ECMAScript HTTP",
|
|
// it assumes that requests are always performed in the context of a browser
|
|
// application, where some network parameters are set by the browser user and
|
|
// should not be modified by Web applications. This API provides access to
|
|
// these network parameters.
|
|
|
|
// NOTE: this is not in the XMLHttpRequest API, and will not work in
|
|
// browsers. It is a stable node-xhr2 API.
|
|
|
|
// @param {Object} options one or more of the options below
|
|
// @option options {?http.Agent} httpAgent the value for the nodejsHttpAgent
|
|
// property (the agent used for HTTP requests)
|
|
// @option options {?https.Agent} httpsAgent the value for the
|
|
// nodejsHttpsAgent property (the agent used for HTTPS requests)
|
|
// @return {undefined} undefined
|
|
nodejsSet(options) {
|
|
var baseUrl, parsedUrl;
|
|
if ('httpAgent' in options) {
|
|
this.nodejsHttpAgent = options.httpAgent;
|
|
}
|
|
if ('httpsAgent' in options) {
|
|
this.nodejsHttpsAgent = options.httpsAgent;
|
|
}
|
|
if ('baseUrl' in options) {
|
|
baseUrl = options.baseUrl;
|
|
if (baseUrl !== null) {
|
|
parsedUrl = url.parse(baseUrl, false, true);
|
|
if (!parsedUrl.protocol) {
|
|
throw new SyntaxError("baseUrl must be an absolute URL");
|
|
}
|
|
}
|
|
this.nodejsBaseUrl = baseUrl;
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
// Default settings for the network configuration not exposed in the XHR API.
|
|
|
|
// NOTE: this is not in the XMLHttpRequest API, and will not work in
|
|
// browsers. It is a stable node-xhr2 API.
|
|
|
|
// @param {Object} options one or more of the options below
|
|
// @option options {?http.Agent} httpAgent the default value for the
|
|
// nodejsHttpAgent property (the agent used for HTTP requests)
|
|
// @option options {https.Agent} httpsAgent the default value for the
|
|
// nodejsHttpsAgent property (the agent used for HTTPS requests)
|
|
// @return {undefined} undefined
|
|
// @see XMLHttpRequest.nodejsSet
|
|
static nodejsSet(options) {
|
|
// "this" will be set to XMLHttpRequest.prototype, so the instance nodejsSet
|
|
// operates on default property values.
|
|
XMLHttpRequest.prototype.nodejsSet(options);
|
|
return void 0;
|
|
}
|
|
|
|
// Sets the readyState property and fires the readystatechange event.
|
|
|
|
// @private
|
|
// @param {Number} newReadyState the new value of readyState
|
|
// @return {undefined} undefined
|
|
_setReadyState(newReadyState) {
|
|
var event;
|
|
this.readyState = newReadyState;
|
|
event = new ProgressEvent('readystatechange');
|
|
this.dispatchEvent(event);
|
|
return void 0;
|
|
}
|
|
|
|
// XMLHttpRequest#send() implementation for the file: protocol.
|
|
|
|
// @private
|
|
_sendFile() {
|
|
if (this._url.method !== 'GET') {
|
|
throw new NetworkError('The file protocol only supports GET');
|
|
}
|
|
throw new Error("Protocol file: not implemented");
|
|
}
|
|
|
|
// XMLHttpRequest#send() implementation for the http: and https: protocols.
|
|
|
|
// @private
|
|
// This method sets the instance variables and calls _sendHxxpRequest(), which
|
|
// is responsible for building a node.js request and firing it off. The code
|
|
// in _sendHxxpRequest() is separated off so it can be reused when handling
|
|
// redirects.
|
|
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#infrastructure-for-the-send()-method
|
|
_sendHttp(data) {
|
|
if (this._sync) {
|
|
throw new Error("Synchronous XHR processing not implemented");
|
|
}
|
|
if ((data != null) && (this._method === 'GET' || this._method === 'HEAD')) {
|
|
console.warn(`Discarding entity body for ${this._method} requests`);
|
|
data = null;
|
|
} else {
|
|
// Send Content-Length: 0
|
|
data || (data = '');
|
|
}
|
|
// NOTE: this is called before finalizeHeaders so that the uploader can
|
|
// figure out Content-Length and Content-Type.
|
|
this.upload._setData(data);
|
|
this._finalizeHeaders();
|
|
this._sendHxxpRequest();
|
|
return void 0;
|
|
}
|
|
|
|
// Sets up and fires off a HTTP/HTTPS request using the node.js API.
|
|
|
|
// @private
|
|
// This method contains the bulk of the XMLHttpRequest#send() implementation,
|
|
// and is also used to issue new HTTP requests when handling HTTP redirects.
|
|
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#infrastructure-for-the-send()-method
|
|
_sendHxxpRequest() {
|
|
var agent, hxxp, request;
|
|
if (this._url.protocol === 'http:') {
|
|
hxxp = http;
|
|
agent = this.nodejsHttpAgent;
|
|
} else {
|
|
hxxp = https;
|
|
agent = this.nodejsHttpsAgent;
|
|
}
|
|
request = hxxp.request({
|
|
hostname: this._url.hostname,
|
|
port: this._url.port,
|
|
path: this._url.path,
|
|
auth: this._url.auth,
|
|
method: this._method,
|
|
headers: this._headers,
|
|
agent: agent
|
|
});
|
|
this._request = request;
|
|
if (this.timeout) {
|
|
request.setTimeout(this.timeout, () => {
|
|
return this._onHttpTimeout(request);
|
|
});
|
|
}
|
|
request.on('response', (response) => {
|
|
return this._onHttpResponse(request, response);
|
|
});
|
|
request.on('error', (error) => {
|
|
return this._onHttpRequestError(request, error);
|
|
});
|
|
this.upload._startUpload(request);
|
|
if (this._request === request) { // An http error might have already fired.
|
|
this._dispatchProgress('loadstart');
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
// Fills in the restricted HTTP headers with default values.
|
|
|
|
// This is called right before the HTTP request is sent off.
|
|
|
|
// @private
|
|
// @return {undefined} undefined
|
|
_finalizeHeaders() {
|
|
var base;
|
|
this._headers['Connection'] = 'keep-alive';
|
|
this._headers['Host'] = this._url.host;
|
|
if (this._anonymous) {
|
|
this._headers['Referer'] = 'about:blank';
|
|
}
|
|
(base = this._headers)['User-Agent'] || (base['User-Agent'] = this._userAgent);
|
|
this.upload._finalizeHeaders(this._headers, this._loweredHeaders);
|
|
return void 0;
|
|
}
|
|
|
|
// Called when the headers of an HTTP response have been received.
|
|
|
|
// @private
|
|
// @param {http.ClientRequest} request the node.js ClientRequest instance that
|
|
// produced this response
|
|
// @param {http.ClientResponse} response the node.js ClientResponse instance
|
|
// passed to
|
|
_onHttpResponse(request, response) {
|
|
var lengthString;
|
|
if (this._request !== request) {
|
|
return;
|
|
}
|
|
// Transparent redirection handling.
|
|
switch (response.statusCode) {
|
|
case 301:
|
|
case 302:
|
|
case 303:
|
|
case 307:
|
|
case 308:
|
|
this._url = this._parseUrl(response.headers['location']);
|
|
this._method = 'GET';
|
|
if ('content-type' in this._loweredHeaders) {
|
|
delete this._headers[this._loweredHeaders['content-type']];
|
|
delete this._loweredHeaders['content-type'];
|
|
}
|
|
// XMLHttpRequestUpload#_finalizeHeaders() sets Content-Type directly.
|
|
if ('Content-Type' in this._headers) {
|
|
delete this._headers['Content-Type'];
|
|
}
|
|
// Restricted headers can't be set by the user, no need to check
|
|
// loweredHeaders.
|
|
delete this._headers['Content-Length'];
|
|
this.upload._reset();
|
|
this._finalizeHeaders();
|
|
this._sendHxxpRequest();
|
|
return;
|
|
}
|
|
this._response = response;
|
|
this._response.on('data', (data) => {
|
|
return this._onHttpResponseData(response, data);
|
|
});
|
|
this._response.on('end', () => {
|
|
return this._onHttpResponseEnd(response);
|
|
});
|
|
this._response.on('close', () => {
|
|
return this._onHttpResponseClose(response);
|
|
});
|
|
this.responseURL = this._url.href.split('#')[0];
|
|
this.status = this._response.statusCode;
|
|
this.statusText = http.STATUS_CODES[this.status];
|
|
this._parseResponseHeaders(response);
|
|
if (lengthString = this._responseHeaders['content-length']) {
|
|
this._totalBytes = parseInt(lengthString);
|
|
this._lengthComputable = true;
|
|
} else {
|
|
this._lengthComputable = false;
|
|
}
|
|
return this._setReadyState(XMLHttpRequest.HEADERS_RECEIVED);
|
|
}
|
|
|
|
// Called when some data has been received on a HTTP connection.
|
|
|
|
// @private
|
|
// @param {http.ClientResponse} response the node.js ClientResponse instance
|
|
// that fired this event
|
|
// @param {String, Buffer} data the data that has been received
|
|
_onHttpResponseData(response, data) {
|
|
if (this._response !== response) {
|
|
return;
|
|
}
|
|
this._responseParts.push(data);
|
|
this._loadedBytes += data.length;
|
|
if (this.readyState !== XMLHttpRequest.LOADING) {
|
|
this._setReadyState(XMLHttpRequest.LOADING);
|
|
}
|
|
return this._dispatchProgress('progress');
|
|
}
|
|
|
|
// Called when the HTTP request finished processing.
|
|
|
|
// @private
|
|
// @param {http.ClientResponse} response the node.js ClientResponse instance
|
|
// that fired this event
|
|
_onHttpResponseEnd(response) {
|
|
if (this._response !== response) {
|
|
return;
|
|
}
|
|
this._parseResponse();
|
|
this._request = null;
|
|
this._response = null;
|
|
this._setReadyState(XMLHttpRequest.DONE);
|
|
this._dispatchProgress('load');
|
|
return this._dispatchProgress('loadend');
|
|
}
|
|
|
|
// Called when the underlying HTTP connection was closed prematurely.
|
|
|
|
// If this method is called, it will be called after or instead of
|
|
// onHttpResponseEnd.
|
|
|
|
// @private
|
|
// @param {http.ClientResponse} response the node.js ClientResponse instance
|
|
// that fired this event
|
|
_onHttpResponseClose(response) {
|
|
var request;
|
|
if (this._response !== response) {
|
|
return;
|
|
}
|
|
request = this._request;
|
|
this._setError();
|
|
request.abort();
|
|
this._setReadyState(XMLHttpRequest.DONE);
|
|
this._dispatchProgress('error');
|
|
return this._dispatchProgress('loadend');
|
|
}
|
|
|
|
// Called when the timeout set on the HTTP socket expires.
|
|
|
|
// @private
|
|
// @param {http.ClientRequest} request the node.js ClientRequest instance that
|
|
// fired this event
|
|
_onHttpTimeout(request) {
|
|
if (this._request !== request) {
|
|
return;
|
|
}
|
|
this._setError();
|
|
request.abort();
|
|
this._setReadyState(XMLHttpRequest.DONE);
|
|
this._dispatchProgress('timeout');
|
|
return this._dispatchProgress('loadend');
|
|
}
|
|
|
|
// Called when something wrong happens on the HTTP socket
|
|
|
|
// @private
|
|
// @param {http.ClientRequest} request the node.js ClientRequest instance that
|
|
// fired this event
|
|
// @param {Error} error emitted exception
|
|
_onHttpRequestError(request, error) {
|
|
if (this._request !== request) {
|
|
return;
|
|
}
|
|
this._setError();
|
|
request.abort();
|
|
this._setReadyState(XMLHttpRequest.DONE);
|
|
this._dispatchProgress('error');
|
|
return this._dispatchProgress('loadend');
|
|
}
|
|
|
|
// Fires an XHR progress event.
|
|
|
|
// @private
|
|
// @param {String} eventType one of the XHR progress event types, such as
|
|
// 'load' and 'progress'
|
|
_dispatchProgress(eventType) {
|
|
var event;
|
|
event = new ProgressEvent(eventType);
|
|
event.lengthComputable = this._lengthComputable;
|
|
event.loaded = this._loadedBytes;
|
|
event.total = this._totalBytes;
|
|
this.dispatchEvent(event);
|
|
return void 0;
|
|
}
|
|
|
|
// Sets up the XHR to reflect the fact that an error has occurred.
|
|
|
|
// The possible errors are a network error, a timeout, or an abort.
|
|
|
|
// @private
|
|
_setError() {
|
|
this._request = null;
|
|
this._response = null;
|
|
this._responseHeaders = null;
|
|
this._responseParts = null;
|
|
return void 0;
|
|
}
|
|
|
|
// Parses a request URL string.
|
|
|
|
// @private
|
|
// This method is a thin wrapper around url.parse() that normalizes HTTP
|
|
// user/password credentials. It is used to parse the URL string passed to
|
|
// XMLHttpRequest#open() and the URLs in the Location headers of HTTP redirect
|
|
// responses.
|
|
|
|
// @param {String} urlString the URL to be parsed
|
|
// @return {Object} parsed URL
|
|
_parseUrl(urlString) {
|
|
var absoluteUrlString, index, password, user, xhrUrl;
|
|
if (this.nodejsBaseUrl === null) {
|
|
absoluteUrlString = urlString;
|
|
} else {
|
|
absoluteUrlString = url.resolve(this.nodejsBaseUrl, urlString);
|
|
}
|
|
xhrUrl = url.parse(absoluteUrlString, false, true);
|
|
xhrUrl.hash = null;
|
|
if (xhrUrl.auth && ((typeof user !== "undefined" && user !== null) || (typeof password !== "undefined" && password !== null))) {
|
|
index = xhrUrl.auth.indexOf(':');
|
|
if (index === -1) {
|
|
if (!user) {
|
|
user = xhrUrl.auth;
|
|
}
|
|
} else {
|
|
if (!user) {
|
|
user = xhrUrl.substring(0, index);
|
|
}
|
|
if (!password) {
|
|
password = xhrUrl.substring(index + 1);
|
|
}
|
|
}
|
|
}
|
|
if (user || password) {
|
|
xhrUrl.auth = `${user}:${password}`;
|
|
}
|
|
return xhrUrl;
|
|
}
|
|
|
|
// Reads the headers from a node.js ClientResponse instance.
|
|
|
|
// @private
|
|
// @param {http.ClientResponse} response the response whose headers will be
|
|
// imported into this XMLHttpRequest's state
|
|
// @return {undefined} undefined
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders()-method
|
|
_parseResponseHeaders(response) {
|
|
var loweredName, name, ref, value;
|
|
this._responseHeaders = {};
|
|
ref = response.headers;
|
|
for (name in ref) {
|
|
value = ref[name];
|
|
loweredName = name.toLowerCase();
|
|
if (this._privateHeaders[loweredName]) {
|
|
continue;
|
|
}
|
|
if (this._mimeOverride !== null && loweredName === 'content-type') {
|
|
value = this._mimeOverride;
|
|
}
|
|
this._responseHeaders[loweredName] = value;
|
|
}
|
|
if (this._mimeOverride !== null && !('content-type' in this._responseHeaders)) {
|
|
this._responseHeaders['content-type'] = this._mimeOverride;
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
// Sets the response and responseText properties when an XHR completes.
|
|
|
|
// @private
|
|
// @return {undefined} undefined
|
|
_parseResponse() {
|
|
var arrayBuffer, buffer, i, j, jsonError, ref, view;
|
|
if (Buffer.concat) {
|
|
buffer = Buffer.concat(this._responseParts);
|
|
} else {
|
|
// node 0.6
|
|
buffer = this._concatBuffers(this._responseParts);
|
|
}
|
|
this._responseParts = null;
|
|
switch (this.responseType) {
|
|
case 'text':
|
|
this._parseTextResponse(buffer);
|
|
break;
|
|
case 'json':
|
|
this.responseText = null;
|
|
try {
|
|
this.response = JSON.parse(buffer.toString('utf-8'));
|
|
} catch (error1) {
|
|
jsonError = error1;
|
|
this.response = null;
|
|
}
|
|
break;
|
|
case 'buffer':
|
|
this.responseText = null;
|
|
this.response = buffer;
|
|
break;
|
|
case 'arraybuffer':
|
|
this.responseText = null;
|
|
arrayBuffer = new ArrayBuffer(buffer.length);
|
|
view = new Uint8Array(arrayBuffer);
|
|
for (i = j = 0, ref = buffer.length; (0 <= ref ? j < ref : j > ref); i = 0 <= ref ? ++j : --j) {
|
|
view[i] = buffer[i];
|
|
}
|
|
this.response = arrayBuffer;
|
|
break;
|
|
default:
|
|
// TODO(pwnall): content-base detection
|
|
this._parseTextResponse(buffer);
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
// Sets response and responseText for a 'text' response type.
|
|
|
|
// @private
|
|
// @param {Buffer} buffer the node.js Buffer containing the binary response
|
|
// @return {undefined} undefined
|
|
_parseTextResponse(buffer) {
|
|
var e;
|
|
try {
|
|
this.responseText = buffer.toString(this._parseResponseEncoding());
|
|
} catch (error1) {
|
|
e = error1;
|
|
// Unknown encoding.
|
|
this.responseText = buffer.toString('binary');
|
|
}
|
|
this.response = this.responseText;
|
|
return void 0;
|
|
}
|
|
|
|
// Figures out the string encoding of the XHR's response.
|
|
|
|
// This is called to determine the encoding when responseText is set.
|
|
|
|
// @private
|
|
// @return {String} a string encoding, e.g. 'utf-8'
|
|
_parseResponseEncoding() {
|
|
var contentType, encoding, match;
|
|
encoding = null;
|
|
if (contentType = this._responseHeaders['content-type']) {
|
|
if (match = /\;\s*charset\=(.*)$/.exec(contentType)) {
|
|
return match[1];
|
|
}
|
|
}
|
|
return 'utf-8';
|
|
}
|
|
|
|
// Buffer.concat implementation for node 0.6.
|
|
|
|
// @private
|
|
// @param {Array<Buffer>} buffers the buffers whose contents will be merged
|
|
// @return {Buffer} same as Buffer.concat(buffers) in node 0.8 and above
|
|
_concatBuffers(buffers) {
|
|
var buffer, j, k, len, len1, length, target;
|
|
if (buffers.length === 0) {
|
|
return Buffer.alloc(0);
|
|
}
|
|
if (buffers.length === 1) {
|
|
return buffers[0];
|
|
}
|
|
length = 0;
|
|
for (j = 0, len = buffers.length; j < len; j++) {
|
|
buffer = buffers[j];
|
|
length += buffer.length;
|
|
}
|
|
target = Buffer.alloc(length);
|
|
length = 0;
|
|
for (k = 0, len1 = buffers.length; k < len1; k++) {
|
|
buffer = buffers[k];
|
|
buffer.copy(target, length);
|
|
length += buffer.length;
|
|
}
|
|
return target;
|
|
}
|
|
|
|
};
|
|
|
|
// @property {function(ProgressEvent)} DOM level 0-style handler for the
|
|
// 'readystatechange' event
|
|
XMLHttpRequest.prototype.onreadystatechange = null;
|
|
|
|
// @property {Number} the current state of the XHR object
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#states
|
|
XMLHttpRequest.prototype.readyState = null;
|
|
|
|
// @property {String, ArrayBuffer, Buffer, Object} processed XHR response
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#the-response-attribute
|
|
XMLHttpRequest.prototype.response = null;
|
|
|
|
// @property {String} response string, if responseType is '' or 'text'
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute
|
|
XMLHttpRequest.prototype.responseText = null;
|
|
|
|
// @property {String} sets the parsing method for the XHR response
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute
|
|
XMLHttpRequest.prototype.responseType = null;
|
|
|
|
// @property {Number} the HTTP
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#the-status-attribute
|
|
XMLHttpRequest.prototype.status = null;
|
|
|
|
// @property {Number} milliseconds to wait for the request to complete
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute
|
|
XMLHttpRequest.prototype.timeout = null;
|
|
|
|
// @property {XMLHttpRequestUpload} the associated upload information
|
|
// @see http://www.w3.org/TR/XMLHttpRequest/#the-upload-attribute
|
|
XMLHttpRequest.prototype.upload = null;
|
|
|
|
// readyState value before XMLHttpRequest#open() is called
|
|
XMLHttpRequest.prototype.UNSENT = 0;
|
|
|
|
// readyState value before XMLHttpRequest#open() is called
|
|
XMLHttpRequest.UNSENT = 0;
|
|
|
|
// readyState value after XMLHttpRequest#open() is called, and before
|
|
// XMLHttpRequest#send() is called; XMLHttpRequest#setRequestHeader() can be
|
|
// called in this state
|
|
XMLHttpRequest.prototype.OPENED = 1;
|
|
|
|
// readyState value after XMLHttpRequest#open() is called, and before
|
|
// XMLHttpRequest#send() is called; XMLHttpRequest#setRequestHeader() can be
|
|
// called in this state
|
|
XMLHttpRequest.OPENED = 1;
|
|
|
|
// readyState value after redirects have been followed and the HTTP headers of
|
|
// the final response have been received
|
|
XMLHttpRequest.prototype.HEADERS_RECEIVED = 2;
|
|
|
|
// readyState value after redirects have been followed and the HTTP headers of
|
|
// the final response have been received
|
|
XMLHttpRequest.HEADERS_RECEIVED = 2;
|
|
|
|
// readyState value when the response entity body is being received
|
|
XMLHttpRequest.prototype.LOADING = 3;
|
|
|
|
// readyState value when the response entity body is being received
|
|
XMLHttpRequest.LOADING = 3;
|
|
|
|
// readyState value after the request has been completely processed
|
|
XMLHttpRequest.prototype.DONE = 4;
|
|
|
|
// readyState value after the request has been completely processed
|
|
XMLHttpRequest.DONE = 4;
|
|
|
|
// @property {http.Agent} the agent option passed to HTTP requests
|
|
|
|
// NOTE: this is not in the XMLHttpRequest API, and will not work in browsers.
|
|
// It is a stable node-xhr2 API that is useful for testing & going through
|
|
// web-proxies.
|
|
XMLHttpRequest.prototype.nodejsHttpAgent = http.globalAgent;
|
|
|
|
// @property {https.Agent} the agent option passed to HTTPS requests
|
|
|
|
// NOTE: this is not in the XMLHttpRequest API, and will not work in browsers.
|
|
// It is a stable node-xhr2 API that is useful for testing & going through
|
|
// web-proxies.
|
|
XMLHttpRequest.prototype.nodejsHttpsAgent = https.globalAgent;
|
|
|
|
// @property {String} the base URL that relative URLs get resolved to
|
|
|
|
// NOTE: this is not in the XMLHttpRequest API, and will not work in browsers.
|
|
// Its browser equivalent is the base URL of the document associated with the
|
|
// Window object. It is a stable node-xhr2 API provided for libraries such as
|
|
// Angular Universal.
|
|
XMLHttpRequest.prototype.nodejsBaseUrl = null;
|
|
|
|
// HTTP methods that are disallowed in the XHR spec.
|
|
|
|
// @private
|
|
// @see Step 6 in http://www.w3.org/TR/XMLHttpRequest/#the-open()-method
|
|
XMLHttpRequest.prototype._restrictedMethods = {
|
|
CONNECT: true,
|
|
TRACE: true,
|
|
TRACK: true
|
|
};
|
|
|
|
// HTTP request headers that are disallowed in the XHR spec.
|
|
|
|
// @private
|
|
// @see Step 5 in
|
|
// http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader()-method
|
|
XMLHttpRequest.prototype._restrictedHeaders = {
|
|
'accept-charset': true,
|
|
'accept-encoding': true,
|
|
'access-control-request-headers': true,
|
|
'access-control-request-method': true,
|
|
connection: true,
|
|
'content-length': true,
|
|
cookie: true,
|
|
cookie2: true,
|
|
date: true,
|
|
dnt: true,
|
|
expect: true,
|
|
host: true,
|
|
'keep-alive': true,
|
|
origin: true,
|
|
referer: true,
|
|
te: true,
|
|
trailer: true,
|
|
'transfer-encoding': true,
|
|
upgrade: true,
|
|
via: true
|
|
};
|
|
|
|
// HTTP response headers that should not be exposed according to the XHR spec.
|
|
|
|
// @private
|
|
// @see Step 3 in
|
|
// http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method
|
|
XMLHttpRequest.prototype._privateHeaders = {
|
|
'set-cookie': true,
|
|
'set-cookie2': true
|
|
};
|
|
|
|
// The default value of the User-Agent header.
|
|
XMLHttpRequest.prototype._userAgent = `Mozilla/5.0 (${os.type()} ${os.arch()}) ` + `node.js/${process.versions.node} v8/${process.versions.v8}`;
|
|
|
|
return XMLHttpRequest;
|
|
|
|
}).call(this);
|
|
|
|
// XMLHttpRequest is the result of require('node-xhr2').
|
|
module.exports = XMLHttpRequest;
|
|
|
|
// Make node-xhr2 work as a drop-in replacement for libraries that promote the
|
|
// following usage pattern:
|
|
// var XMLHttpRequest = require('xhr-library-name').XMLHttpRequest
|
|
XMLHttpRequest.XMLHttpRequest = XMLHttpRequest;
|
|
|
|
// This file defines the custom errors used in the XMLHttpRequest specification.
|
|
|
|
// Thrown if the XHR security policy is violated.
|
|
SecurityError = class SecurityError extends Error {
|
|
// @private
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
};
|
|
|
|
// Thrown if the XHR security policy is violated.
|
|
XMLHttpRequest.SecurityError = SecurityError;
|
|
|
|
// Usually thrown if the XHR is in the wrong readyState for an operation.
|
|
InvalidStateError = class InvalidStateError extends Error {
|
|
// @private
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
};
|
|
|
|
// Usually thrown if the XHR is in the wrong readyState for an operation.
|
|
InvalidStateError = class InvalidStateError extends Error {};
|
|
|
|
XMLHttpRequest.InvalidStateError = InvalidStateError;
|
|
|
|
// Thrown if there is a problem with the URL passed to the XHR.
|
|
NetworkError = class NetworkError extends Error {
|
|
// @private
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
};
|
|
|
|
// Thrown if parsing URLs errors out.
|
|
XMLHttpRequest.SyntaxError = SyntaxError;
|
|
|
|
SyntaxError = class SyntaxError extends Error {
|
|
// @private:
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
};
|
|
|
|
ProgressEvent = (function() {
|
|
// http://xhr.spec.whatwg.org/#interface-progressevent
|
|
class ProgressEvent {
|
|
// Creates a new event.
|
|
|
|
// @param {String} type the event type, e.g. 'readystatechange'; must be
|
|
// lowercased
|
|
constructor(type) {
|
|
this.type = type;
|
|
this.target = null;
|
|
this.currentTarget = null;
|
|
this.lengthComputable = false;
|
|
this.loaded = 0;
|
|
this.total = 0;
|
|
}
|
|
|
|
};
|
|
|
|
// Getting the time from the OS is expensive, skip on that for now.
|
|
// @timeStamp = Date.now()
|
|
|
|
// @property {Boolean} for compatibility with DOM events
|
|
ProgressEvent.prototype.bubbles = false;
|
|
|
|
// @property {Boolean} for fompatibility with DOM events
|
|
ProgressEvent.prototype.cancelable = false;
|
|
|
|
// @property {XMLHttpRequest} the request that caused this event
|
|
ProgressEvent.prototype.target = null;
|
|
|
|
// @property {Number} number of bytes that have already been downloaded or
|
|
// uploaded
|
|
ProgressEvent.prototype.loaded = null;
|
|
|
|
// @property {Boolean} true if the Content-Length response header is available
|
|
// and the value of the event's total property is meaningful
|
|
ProgressEvent.prototype.lengthComputable = null;
|
|
|
|
// @property {Number} number of bytes that will be downloaded or uploaded by
|
|
// the request that fired the event
|
|
ProgressEvent.prototype.total = null;
|
|
|
|
return ProgressEvent;
|
|
|
|
}).call(this);
|
|
|
|
// The XHR spec exports the ProgressEvent constructor.
|
|
XMLHttpRequest.ProgressEvent = ProgressEvent;
|
|
|
|
// @see http://xhr.spec.whatwg.org/#interface-xmlhttprequest
|
|
XMLHttpRequestUpload = class XMLHttpRequestUpload extends XMLHttpRequestEventTarget {
|
|
// @private
|
|
// @param {XMLHttpRequest} the XMLHttpRequest that this upload object is
|
|
// associated with
|
|
constructor(request) {
|
|
super();
|
|
this._request = request;
|
|
this._reset();
|
|
}
|
|
|
|
// Sets up this Upload to handle a new request.
|
|
|
|
// @private
|
|
// @return {undefined} undefined
|
|
_reset() {
|
|
this._contentType = null;
|
|
this._body = null;
|
|
return void 0;
|
|
}
|
|
|
|
// Implements the upload-related part of the send() XHR specification.
|
|
|
|
// @private
|
|
// @param {?String, ?Buffer, ?ArrayBufferView} data the argument passed to
|
|
// XMLHttpRequest#send()
|
|
// @return {undefined} undefined
|
|
// @see step 4 of http://www.w3.org/TR/XMLHttpRequest/#the-send()-method
|
|
_setData(data) {
|
|
var body, i, j, k, offset, ref, ref1, view;
|
|
if (typeof data === 'undefined' || data === null) {
|
|
return;
|
|
}
|
|
if (typeof data === 'string') {
|
|
// DOMString
|
|
if (data.length !== 0) {
|
|
this._contentType = 'text/plain;charset=UTF-8';
|
|
}
|
|
this._body = Buffer.from(data, 'utf8');
|
|
} else if (Buffer.isBuffer(data)) {
|
|
// node.js Buffer
|
|
this._body = data;
|
|
} else if (data instanceof ArrayBuffer) {
|
|
// ArrayBuffer arguments were supported in an old revision of the spec.
|
|
body = Buffer.alloc(data.byteLength);
|
|
view = new Uint8Array(data);
|
|
for (i = j = 0, ref = data.byteLength; (0 <= ref ? j < ref : j > ref); i = 0 <= ref ? ++j : --j) {
|
|
body[i] = view[i];
|
|
}
|
|
this._body = body;
|
|
} else if (data.buffer && data.buffer instanceof ArrayBuffer) {
|
|
// ArrayBufferView
|
|
body = Buffer.alloc(data.byteLength);
|
|
offset = data.byteOffset;
|
|
view = new Uint8Array(data.buffer);
|
|
for (i = k = 0, ref1 = data.byteLength; (0 <= ref1 ? k < ref1 : k > ref1); i = 0 <= ref1 ? ++k : --k) {
|
|
body[i] = view[i + offset];
|
|
}
|
|
this._body = body;
|
|
} else {
|
|
// NOTE: diverging from the XHR specification of coercing everything else
|
|
// to Strings via toString() because that behavior masks bugs and is
|
|
// rarely useful
|
|
throw new Error(`Unsupported send() data ${data}`);
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
// Updates the HTTP headers right before the request is sent.
|
|
|
|
// This is used to set data-dependent headers such as Content-Length and
|
|
// Content-Type.
|
|
|
|
// @private
|
|
// @param {Object<String, String>} headers the HTTP headers to be sent
|
|
// @param {Object<String, String>} loweredHeaders maps lowercased HTTP header
|
|
// names (e.g., 'content-type') to the actual names used in the headers
|
|
// parameter (e.g., 'Content-Type')
|
|
// @return {undefined} undefined
|
|
_finalizeHeaders(headers, loweredHeaders) {
|
|
if (this._contentType) {
|
|
if (!('content-type' in loweredHeaders)) {
|
|
headers['Content-Type'] = this._contentType;
|
|
}
|
|
}
|
|
if (this._body) {
|
|
// Restricted headers can't be set by the user, no need to check
|
|
// loweredHeaders.
|
|
headers['Content-Length'] = this._body.length.toString();
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
// Starts sending the HTTP request data.
|
|
|
|
// @private
|
|
// @param {http.ClientRequest} request the HTTP request
|
|
// @return {undefined} undefined
|
|
_startUpload(request) {
|
|
if (this._body) {
|
|
request.write(this._body);
|
|
}
|
|
request.end();
|
|
return void 0;
|
|
}
|
|
|
|
};
|
|
|
|
// Export the XMLHttpRequestUpload constructor.
|
|
XMLHttpRequest.XMLHttpRequestUpload = XMLHttpRequestUpload;
|
|
|
|
}).call(this);
|