hoxy
Version:
Web-hacking proxy API for node
316 lines (275 loc) • 8.13 kB
JavaScript
/*
* Copyright (c) 2015 by Greg Reimer <gregreimer@gmail.com>
* MIT License. See mit-license.txt for more info.
*/
'use strict';
var _get = require('babel-runtime/helpers/get')['default'];
var _inherits = require('babel-runtime/helpers/inherits')['default'];
var _createClass = require('babel-runtime/helpers/create-class')['default'];
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
var _Object$keys = require('babel-runtime/core-js/object/keys')['default'];
var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
Object.defineProperty(exports, '__esModule', {
value: true
});
var _body = require('./body');
var _body2 = _interopRequireDefault(_body);
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _url = require('url');
var _url2 = _interopRequireDefault(_url);
var _querystring = require('querystring');
var _querystring2 = _interopRequireDefault(_querystring);
var _assert = require('assert');
var _assert2 = _interopRequireDefault(_assert);
var validProtocols = {
'http:': true,
'https:': true
};
var removeHeaders = {
'accept-encoding': true, // until proxy handles gzip
'proxy-connection': true,
'proxy-authorization': true
};
var nonEntityMethods = {
GET: true,
HEAD: true,
TRACE: true
};
/**
* Represents an HTTP request.
*/
var Request = (function (_Body) {
_inherits(Request, _Body);
function Request() {
_classCallCheck(this, Request);
_get(Object.getPrototypeOf(Request.prototype), 'constructor', this).call(this);
this._data = { slow: {} };
}
/**
* Getter/setter for HTTP protocol, e.g. 'http:'
*/
_createClass(Request, [{
key: 'fullUrl',
value: function fullUrl(u) {
if (u) {
var purl = _url2['default'].parse(u);
if (purl.protocol) {
this.protocol = purl.protocol;
}
if (purl.hostname) {
this.hostname = purl.hostname;
}
if (purl.port) {
this.port = purl.port;
} else {
this.port = undefined;
}
if (purl.path) {
this.url = purl.path;
}
return undefined;
} else {
var portStr = '';
var declaredPort = this.port;
if (declaredPort) {
portStr = ':' + declaredPort;
}
return this.protocol + '//' + this.hostname + portStr + this.url;
}
}
}, {
key: '_setHttpSource',
value: function _setHttpSource(inReq, reverse) {
var u = inReq.url;
if (reverse) {
u = _url2['default'].resolve(reverse, u);
}
var purl = _url2['default'].parse(u);
if (reverse) {
inReq.headers.host = purl.host;
if (!purl.protocol) {
throw new Error('missing protocol on reverse proxy');
}
}
var host = (function () {
var aHost = inReq.headers.host;
var result = {};
if (aHost) {
var matches = aHost.match(/^([^:]+)(:([\d]+))?$/);
if (matches) {
result.name = matches[1];
var _port = parseInt(matches[3]);
if (_port) {
result.port = _port;
}
}
}
return result;
})();
this.httpVersion = inReq.httpVersion;
this.headers = inReq.headers;
this.protocol = purl.protocol || 'http:';
this.hostname = purl.hostname || host.name;
var port = purl.port || host.port;
if (port) {
this.port = port;
}
//this.port = purl.port || host.port || this._getDefaultPort()
this.method = inReq.method;
this.url = purl.path;
inReq._isOriginal = true;
this._source = inReq;
}
/**
* Returns the default port given the current protocol.
*/
}, {
key: '_getDefaultPort',
value: function _getDefaultPort() {
return this.protocol === 'https:' ? 443 : 80;
}
// TODO: emit debug log events for things that are changed.
}, {
key: '_finalize',
value: function _finalize() {
var _this = this;
if (nonEntityMethods.hasOwnProperty(this.method)) {
this.string = ''; // TODO: test
}
if (!this._source) {
this.string = ''; // TODO: test?
}
if (!this._source._isOriginal) {
delete this.headers['content-length'];
if (typeof this._source.finalize === 'function') {
this._source.finalize();
}
if (typeof this._source.size === 'function') {
this.headers['content-length'] = this._source.size();
}
}
_Object$keys(this.headers).forEach(function (name) {
// TODO: test
if (removeHeaders.hasOwnProperty(name)) {
delete _this.headers[name];
} else if (_this.headers[name] === undefined) {
delete _this.headers[name];
}
});
// TODO: test host header correction
var portStr = '';
var declaredPort = this.port;
if (declaredPort) {
portStr += ':' + declaredPort;
}
this.headers.host = this.hostname + portStr;
return this;
}
}, {
key: 'protocol',
get: function get() {
return this._getRawDataItem('protocol');
},
set: function set(protocol) {
if (!validProtocols.hasOwnProperty(protocol)) {
throw new Error('invalid protocol: ' + protocol); // TODO: test this
}
this._setRawDataItem('protocol', protocol);
}
/**
* Getter/setter for host name. Does not incude port.
*/
}, {
key: 'hostname',
get: function get() {
return this._getRawDataItem('hostname');
},
set: function set(hostname) {
if (!hostname) {
throw new Error('invalid hostname: ' + hostname); // TODO: test this
}
this._setRawDataItem('hostname', hostname);
}
/**
* Getter/setter for port.
*/
}, {
key: 'port',
get: function get() {
return this._getRawDataItem('port');
},
set: function set(port) {
if (port === undefined) {
this._setRawDataItem('port', undefined);
} else {
var parsedPort = parseInt(port);
if (!parsedPort) {
throw new Error('invalid port: ' + port); // TODO: test this
}
this._setRawDataItem('port', parsedPort);
}
}
/**
* Getter/setter for HTTP method.
*/
}, {
key: 'method',
get: function get() {
return this._getRawDataItem('method');
},
set: function set(method) {
if (!method) {
throw new Error('invalid method'); // TODO: test this
}
this._setRawDataItem('method', method.toUpperCase());
}
/**
* Getter/setter for URL. Root-relative.
*/
}, {
key: 'url',
get: function get() {
return this._getRawDataItem('url');
},
set: function set(aUrl) {
if (!/^\//.test(aUrl)) {
throw new Error('invalid url, must start with /'); // TODO: test this
}
this._setRawDataItem('url', aUrl);
}
/**
* Getter/setter for URL. Root-relative.
*/
}, {
key: 'query',
get: function get() {
var aUrl = this._getRawDataItem('url');
var pUrl = _url2['default'].parse(aUrl, true);
return pUrl.query || {};
},
set: function set(params) {
(0, _assert2['default'])(typeof params === 'object', 'params not an object');
var aUrl = this._getRawDataItem('url');
var pUrl = _url2['default'].parse(aUrl, true);
var search = _querystring2['default'].stringify(params);
pUrl.search = search;
aUrl = _url2['default'].format(pUrl);
this._setRawDataItem('url', aUrl);
}
/**
* Getter/setter for HTTP request header object.
*/
}, {
key: 'headers',
get: function get() {
return this._getRawDataItem('headers');
},
set: function set(headers) {
this._setRawDataItem('headers', _lodash2['default'].extend({}, headers));
}
}]);
return Request;
})(_body2['default']);
exports['default'] = Request;
module.exports = exports['default'];