var Stream = require('stream'); var Response = require('./response'); var Base64 = require('Base64'); var inherits = require('inherits'); var Request = module.exports = function (xhr, params) { var self = this; self.writable = true; self.xhr = xhr; self.body = []; self.uri = (params.scheme || 'http') + '://' + params.host + (params.port ? ':' + params.port : '') + (params.path || '/') ; if (typeof params.withCredentials === 'undefined') { params.withCredentials = true; } try { xhr.withCredentials = params.withCredentials } catch (e) {} xhr.open( params.method || 'GET', self.uri, true ); self._headers = {}; if (params.headers) { var keys = objectKeys(params.headers); for (var i = 0; i < keys.length; i++) { var key = keys[i]; if (!self.isSafeRequestHeader(key)) continue; var value = params.headers[key]; self.setHeader(key, value); } } if (params.auth) { //basic auth this.setHeader('Authorization', 'Basic ' + Base64.btoa(params.auth)); } var res = new Response; res.on('close', function () { self.emit('close'); }); res.on('ready', function () { self.emit('response', res); }); xhr.onreadystatechange = function () { // Fix for IE9 bug // SCRIPT575: Could not complete the operation due to error c00c023f // It happens when a request is aborted, calling the success callback anyway with readyState === 4 if (xhr.__aborted) return; res.handle(xhr); }; }; inherits(Request, Stream); Request.prototype.setHeader = function (key, value) { this._headers[key.toLowerCase()] = value }; Request.prototype.getHeader = function (key) { return this._headers[key.toLowerCase()] }; Request.prototype.removeHeader = function (key) { delete this._headers[key.toLowerCase()] }; Request.prototype.write = function (s) { this.body.push(s); }; Request.prototype.destroy = function (s) { this.xhr.__aborted = true; this.xhr.abort(); this.emit('close'); }; Request.prototype.end = function (s) { if (s !== undefined) this.body.push(s); var keys = objectKeys(this._headers); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = this._headers[key]; if (isArray(value)) { for (var j = 0; j < value.length; j++) { this.xhr.setRequestHeader(key, value[j]); } } else this.xhr.setRequestHeader(key, value) } if (this.body.length === 0) { this.xhr.send(''); } else if (typeof this.body[0] === 'string') { this.xhr.send(this.body.join('')); } else if (isArray(this.body[0])) { var body = []; for (var i = 0; i < this.body.length; i++) { body.push.apply(body, this.body[i]); } this.xhr.send(body); } else if (/Array/.test(Object.prototype.toString.call(this.body[0]))) { var len = 0; for (var i = 0; i < this.body.length; i++) { len += this.body[i].length; } var body = new(this.body[0].constructor)(len); var k = 0; for (var i = 0; i < this.body.length; i++) { var b = this.body[i]; for (var j = 0; j < b.length; j++) { body[k++] = b[j]; } } this.xhr.send(body); } else { var body = ''; for (var i = 0; i < this.body.length; i++) { body += this.body[i].toString(); } this.xhr.send(body); } }; // Taken from http://dxr.mozilla.org/mozilla/mozilla-central/content/base/src/nsXMLHttpRequest.cpp.html Request.unsafeHeaders = [ "accept-charset", "accept-encoding", "access-control-request-headers", "access-control-request-method", "connection", "content-length", "cookie", "cookie2", "content-transfer-encoding", "date", "expect", "host", "keep-alive", "origin", "referer", "te", "trailer", "transfer-encoding", "upgrade", "user-agent", "via" ]; Request.prototype.isSafeRequestHeader = function (headerName) { if (!headerName) return false; return indexOf(Request.unsafeHeaders, headerName.toLowerCase()) === -1; }; var objectKeys = Object.keys || function (obj) { var keys = []; for (var key in obj) keys.push(key); return keys; }; var isArray = Array.isArray || function (xs) { return Object.prototype.toString.call(xs) === '[object Array]'; }; var indexOf = function (xs, x) { if (xs.indexOf) return xs.indexOf(x); for (var i = 0; i < xs.length; i++) { if (xs[i] === x) return i; } return -1; };