HTTPServer.js.html 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>HTTPServer.js - Documentation</title>
  6. <script src="scripts/prettify/prettify.js"></script>
  7. <script src="scripts/prettify/lang-css.js"></script>
  8. <!--[if lt IE 9]>
  9. <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
  10. <![endif]-->
  11. <link type="text/css" rel="stylesheet" href="styles/prettify.css">
  12. <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
  13. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  14. </head>
  15. <body>
  16. <input type="checkbox" id="nav-trigger" class="nav-trigger" />
  17. <label for="nav-trigger" class="navicon-button x">
  18. <div class="navicon"></div>
  19. </label>
  20. <label for="nav-trigger" class="overlay"></label>
  21. <nav>
  22. <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="ControlServer.html">ControlServer</a><ul class='methods'><li data-type='method'><a href="ControlServer.html#.instance_info">instance_info</a></li><li data-type='method'><a href="ControlServer.html#close">close</a></li><li data-type='method'><a href="ControlServer.html#createDNSServer">createDNSServer</a></li><li data-type='method'><a href="ControlServer.html#createHTTPServer">createHTTPServer</a></li><li data-type='method'><a href="ControlServer.html#createSOCKSServer">createSOCKSServer</a></li><li data-type='method'><a href="ControlServer.html#createTorPool">createTorPool</a></li><li data-type='method'><a href="ControlServer.html#listen">listen</a></li><li data-type='method'><a href="ControlServer.html#listenTcp">listenTcp</a></li><li data-type='method'><a href="ControlServer.html#listenWs">listenWs</a></li></ul></li><li><a href="DNSServer.html">DNSServer</a><ul class='methods'><li data-type='method'><a href="DNSServer.html#listen">listen</a></li></ul></li><li><a href="HTTPServer.html">HTTPServer</a><ul class='methods'><li data-type='method'><a href="HTTPServer.html#listen">listen</a></li></ul></li><li><a href="SOCKSServer.html">SOCKSServer</a><ul class='methods'><li data-type='method'><a href="SOCKSServer.html#listen">listen</a></li></ul></li><li><a href="TorPool.html">TorPool</a><ul class='methods'><li data-type='method'><a href="TorPool.html#add">add</a></li><li data-type='method'><a href="TorPool.html#add_instance_to_group">add_instance_to_group</a></li><li data-type='method'><a href="TorPool.html#add_instance_to_group_at">add_instance_to_group_at</a></li><li data-type='method'><a href="TorPool.html#add_instance_to_group_by_name">add_instance_to_group_by_name</a></li><li data-type='method'><a href="TorPool.html#create">create</a></li><li data-type='method'><a href="TorPool.html#create_instance">create_instance</a></li><li data-type='method'><a href="TorPool.html#exit">exit</a></li><li data-type='method'><a href="TorPool.html#get_config_at">get_config_at</a></li><li data-type='method'><a href="TorPool.html#get_config_by_name">get_config_by_name</a></li><li data-type='method'><a href="TorPool.html#instance_at">instance_at</a></li><li data-type='method'><a href="TorPool.html#instance_by_name">instance_by_name</a></li><li data-type='method'><a href="TorPool.html#instances_by_group">instances_by_group</a></li><li data-type='method'><a href="TorPool.html#new_identites">new_identites</a></li><li data-type='method'><a href="TorPool.html#new_identites_by_group">new_identites_by_group</a></li><li data-type='method'><a href="TorPool.html#new_identity_at">new_identity_at</a></li><li data-type='method'><a href="TorPool.html#new_identity_by_name">new_identity_by_name</a></li><li data-type='method'><a href="TorPool.html#next">next</a></li><li data-type='method'><a href="TorPool.html#next_by_group">next_by_group</a></li><li data-type='method'><a href="TorPool.html#remove">remove</a></li><li data-type='method'><a href="TorPool.html#remove_at">remove_at</a></li><li data-type='method'><a href="TorPool.html#remove_by_name">remove_by_name</a></li><li data-type='method'><a href="TorPool.html#remove_instance_from_group">remove_instance_from_group</a></li><li data-type='method'><a href="TorPool.html#remove_instance_from_group_at">remove_instance_from_group_at</a></li><li data-type='method'><a href="TorPool.html#remove_instance_from_group_by_name">remove_instance_from_group_by_name</a></li><li data-type='method'><a href="TorPool.html#set_config_all">set_config_all</a></li><li data-type='method'><a href="TorPool.html#set_config_at">set_config_at</a></li><li data-type='method'><a href="TorPool.html#set_config_by_group">set_config_by_group</a></li><li data-type='method'><a href="TorPool.html#set_config_by_name">set_config_by_name</a></li><li data-type='method'><a href="TorPool.html#signal_all">signal_all</a></li><li data-type='method'><a href="TorPool.html#signal_at">signal_at</a></li><li data-type='method'><a href="TorPool.html#signal_by_group">signal_by_group</a></li><li data-type='method'><a href="TorPool.html#signal_by_name">signal_by_name</a></li></ul></li><li><a href="TorProcess.html">TorProcess</a><ul class='methods'><li data-type='method'><a href="TorProcess.html#create">create</a></li><li data-type='method'><a href="TorProcess.html#exit">exit</a></li><li data-type='method'><a href="TorProcess.html#get_config">get_config</a></li><li data-type='method'><a href="TorProcess.html#new_identity">new_identity</a></li><li data-type='method'><a href="TorProcess.html#set_config">set_config</a></li><li data-type='method'><a href="TorProcess.html#signal">signal</a></li></ul></li></ul><h3>Modules</h3><ul><li><a href="module-tor-router.html">tor-router</a></li><li><a href="module-tor-router_ControlServer.html">tor-router/ControlServer</a></li><li><a href="module-tor-router_default_config.html">tor-router/default_config</a></li><li><a href="module-tor-router_default_ports.html">tor-router/default_ports</a></li><li><a href="module-tor-router_DNSServer.html">tor-router/DNSServer</a></li><li><a href="module-tor-router_HTTPServer.html">tor-router/HTTPServer</a></li><li><a href="module-tor-router_launch.html">tor-router/launch</a></li><li><a href="module-tor-router_nconf_load_env.html">tor-router/nconf_load_env</a></li><li><a href="module-tor-router_SOCKSServer.html">tor-router/SOCKSServer</a></li><li><a href="module-tor-router_TorPool.html">tor-router/TorPool</a></li><li><a href="module-tor-router_TorProcess.html">tor-router/TorProcess</a></li><li><a href="module-tor-router_winston_silent_logger.html">tor-router/winston_silent_logger</a></li></ul><h3>Events</h3><ul><li><a href="DNSServer.html#event:instance-connection">instance-connection</a></li><li><a href="HTTPServer.html#event:instance-connection">instance-connection</a></li><li><a href="SOCKSServer.html#event:instance-connection">instance-connection</a></li><li><a href="TorPool.html#event:instance_created">instance_created</a></li><li><a href="TorProcess.html#event:control_listen">control_listen</a></li><li><a href="TorProcess.html#event:controller_ready">controller_ready</a></li><li><a href="TorProcess.html#event:dns_listen">dns_listen</a></li><li><a href="TorProcess.html#event:error">error</a></li><li><a href="TorProcess.html#event:process_exit">process_exit</a></li><li><a href="TorProcess.html#event:ready">ready</a></li><li><a href="TorProcess.html#event:socks_listen">socks_listen</a></li></ul><h3>Global</h3><ul><li><a href="global.html#assembleHost">assembleHost</a></li><li><a href="global.html#cleanUp">cleanUp</a></li><li><a href="global.html#env_whitelist">env_whitelist</a></li><li><a href="global.html#logger">logger</a></li><li><a href="global.html#main">main</a></li><li><a href="global.html#nconf">nconf</a></li><li><a href="global.html#REALM">REALM</a></li><li><a href="global.html#setup_nconf_env">setup_nconf_env</a></li><li><a href="global.html#TOR_ROUTER_PROXY_AGENT">TOR_ROUTER_PROXY_AGENT</a></li></ul>
  23. </nav>
  24. <div id="main">
  25. <h1 class="page-title">HTTPServer.js</h1>
  26. <section>
  27. <article>
  28. <pre class="prettyprint source linenums"><code>const http = require('http');
  29. const URL = require('url');
  30. const { Server } = http;
  31. const Promise = require('bluebird');
  32. const socks = require('socksv5');
  33. /**
  34. * Value of the "Proxy-Agent" header that will be sent with each http-connect (https) request
  35. * @constant
  36. * @type {string}
  37. * @default
  38. */
  39. const TOR_ROUTER_PROXY_AGENT = 'tor-router';
  40. /**
  41. * What will show up when an unauthenticated user attempts to connect when an invalid username
  42. * @constant
  43. * @type {string}
  44. * @default
  45. */
  46. const REALM = 'Name of instance to route to';
  47. /**
  48. * A HTTP(S) proxy server that will route requests to instances in the TorPool provided.
  49. * @extends Server
  50. */
  51. class HTTPServer extends Server {
  52. /**
  53. * Binds the server to a port and IP Address.
  54. *
  55. * @async
  56. * @param {number} port - The port to bind to
  57. * @param {string} [host="::"] - Address to bind to. Will default to :: or 0.0.0.0 if not specified.
  58. * @returns {Promise}
  59. *
  60. */
  61. async listen() {
  62. return await new Promise((resolve, reject) => {
  63. let args = Array.from(arguments);
  64. let inner_func = super.listen;
  65. args.push(() => {
  66. let args = Array.from(arguments);
  67. resolve.apply(args);
  68. });
  69. inner_func.apply(this, args);
  70. });
  71. }
  72. /**
  73. * Handles username authentication for HTTP requests
  74. * @param {ClientRequest} req - Incoming HTTP request
  75. * @param {ClientResponse} res - Outgoing HTTP response
  76. * @private
  77. */
  78. authenticate_user_http(req, res) {
  79. return this.authenticate_user(req, () => {
  80. res.writeHead(407, { 'Proxy-Authenticate': `Basic realm="${REALM}"` });
  81. res.end();
  82. return false;
  83. })
  84. }
  85. /**
  86. * Handles username authentication for HTTP-Connect requests
  87. * @param {ClientRequest} req - Incoming HTTP request
  88. * @param {Socket} socket - Inbound HTTP-Connect socket
  89. * @private
  90. */
  91. authenticate_user_connect(req, socket) {
  92. return this.authenticate_user(req, () => {
  93. socket.write(`HTTP/1.1 407 Proxy Authentication Required\r\n'+'Proxy-Authenticate: Basic realm="${REALM}"\r\n` +'\r\n');
  94. socket.end();
  95. return false;
  96. })
  97. }
  98. /**
  99. * Checks the username provided against all groups (for "group" mode) or all instances (for "individual" mode).
  100. * @param {ClientRequest} req - Incoming HTTP request
  101. * @param {Function} deny - Function that when called will deny the connection to the proxy server and prompt the user for credentials (HTTP 407).
  102. * @private
  103. * @throws If the {@link HTTPServer#proxy_by_name} mode is invalid
  104. */
  105. authenticate_user(req, deny) {
  106. if (!this.proxy_by_name)
  107. return true;
  108. let deny_un = this.proxy_by_name.deny_unidentified_users;
  109. let header = req.headers['authorization'] || req.headers['proxy-authorization'];
  110. if (!header &amp;&amp; deny_un) return deny();
  111. else if (!header) return true;
  112. let token = header.split(/\s+/).pop();
  113. if (!token &amp;&amp; deny_un) return deny();
  114. else if (!token) return true;
  115. let buf = new Buffer.from(token, 'base64').toString();
  116. if ( !buf &amp;&amp; deny_un ) return deny();
  117. else if (!buf) return true;
  118. let username = buf.split(/:/).shift();
  119. if ( !username &amp;&amp; deny_un ) return deny();
  120. else if (!username) return true;
  121. let instance;
  122. if (this.proxy_by_name.mode === 'individual')
  123. instance = this.tor_pool.instance_by_name(username);
  124. else if (this.proxy_by_name.mode === 'group') {
  125. if (!this.tor_pool.group_names.has(username)) return deny();
  126. instance = this.tor_pool.next_by_group(username);
  127. }
  128. else
  129. throw Error(`Unknown "proxy_by_name" mode ${this.proxy_by_name.mode}`);
  130. if (!instance) return deny();
  131. req.instance = instance;
  132. return true;
  133. }
  134. /**
  135. * Creates an instance of `HTTPServer`.
  136. * @param {TorPool} tor_pool - The pool of instances that will be used for requests.
  137. * @param {Logger} [logger] - Winston logger that will be used for logging. If not specified will disable logging.
  138. * @param {ProxyByNameConfig} [proxy_by_name] - Enable routing to specific instances or groups of instances using the username field (http://instance-1:@my-server:9050) when connecting.
  139. */
  140. constructor(tor_pool, logger, proxy_by_name) {
  141. /**
  142. * Handles incoming HTTP Connections.
  143. * @function handle_http_connections
  144. * @param {ClientRequest} - Incoming HTTP request.
  145. * @param {ClientResponse} - Outgoing HTTP response.
  146. * @private
  147. */
  148. const handle_http_connections = (req, res) => {
  149. if (!this.authenticate_user_http(req, res))
  150. return;
  151. let { instance } = req;
  152. let url = URL.parse(req.url);
  153. url.port = url.port || 80;
  154. let buffer = [];
  155. function onIncomingData(chunk) {
  156. buffer.push(chunk);
  157. }
  158. function preConnectClosed() {
  159. req.finished = true;
  160. }
  161. req.on('data', onIncomingData);
  162. req.on('end', preConnectClosed);
  163. req.on('error', function (err) {
  164. this.logger.error("[http-proxy]: an error occured: "+err.message);
  165. });
  166. let connect = (tor_instance) => {
  167. let source = { hostname: req.connection.remoteAddress, port: req.connection.remotePort, proto: 'http', by_name: Boolean(instance) };
  168. let socks_port = tor_instance.socks_port;
  169. let proxy_req = http.request({
  170. method: req.method,
  171. hostname: url.hostname,
  172. port: url.port,
  173. path: url.path,
  174. headers: req.headers,
  175. agent: socks.HttpAgent({
  176. proxyHost: '127.0.0.1',
  177. proxyPort: socks_port,
  178. auths: [ socks.auth.None() ],
  179. localDNS: false
  180. })
  181. }, (proxy_res) => {
  182. /**
  183. * Fires when the proxy has made a connection through an instance using HTTP or HTTP-Connect.
  184. *
  185. * @event HTTPServer#instance-connection
  186. * @param {TorProcess} instance - Instance that has been connected to.
  187. * @param {InstanceConnectionSource} source - Details on the source of the connection.
  188. */
  189. this.emit('instance_connection', tor_instance, source);
  190. this.logger.verbose(`[http-proxy]: ${source.hostname}:${source.port} → 127.0.0.1:${socks_port}${tor_instance.definition.Name ? ' ('+tor_instance.definition.Name+')' : '' } → ${url.hostname}:${url.port}`);
  191. proxy_res.on('data', (chunk) => {
  192. res.write(chunk);
  193. });
  194. proxy_res.on('end', () => {
  195. res.end();
  196. });
  197. res.writeHead(proxy_res.statusCode, proxy_res.headers);
  198. });
  199. req.removeListener('data', onIncomingData);
  200. req.on('data', (chunk) => {
  201. proxy_req.write(chunk);
  202. })
  203. req.on('end', () => {
  204. proxy_req.end();
  205. })
  206. while (buffer.length) {
  207. proxy_req.write(buffer.shift());
  208. }
  209. if (req.finished)
  210. proxy_req.end();
  211. };
  212. if (instance) {
  213. if (instance.ready) {
  214. connect(instance);
  215. }
  216. else {
  217. this.logger.debug(`[http-proxy]: a connection has been attempted to "${instance.instance_name}", but it is not live... waiting for the instance to come online`);
  218. instance.once('ready', (() => connect(instance)));
  219. }
  220. }
  221. else if (this.tor_pool.instances.length) {
  222. connect(tor_pool.next());
  223. } else {
  224. this.logger.debug(`[http-proxy]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online`);
  225. tor_pool.once('instance_created', connect);
  226. }
  227. }
  228. /**
  229. * Handles incoming HTTP-Connect connections.
  230. * @function handle_connect_connections
  231. * @param {ClientRequest} req - Incoming HTTP Request.
  232. * @param {Socket} inbound_socket - Incoming socket.
  233. * @param {Buffer|string} head - HTTP Request head.
  234. * @private
  235. */
  236. const handle_connect_connections = (req, inbound_socket, head) => {
  237. if (!this.authenticate_user_connect(req, inbound_socket))
  238. return;
  239. let { instance } = req;
  240. let hostname = req.url.split(':').shift();
  241. let port = Number(req.url.split(':').pop());
  242. let connect = (tor_instance) => {
  243. let source = { hostname: req.connection.remoteAddress, port: req.connection.remotePort, proto: 'http-connect', by_name: Boolean(instance) };
  244. let socks_port = tor_instance.socks_port;
  245. var outbound_socket;
  246. let onClose = (error) => {
  247. inbound_socket &amp;&amp; inbound_socket.end();
  248. outbound_socket &amp;&amp; outbound_socket.end();
  249. inbound_socket = outbound_socket = void(0);
  250. if (error instanceof Error)
  251. this.logger.error(`[http-connect]: an error occured: ${error.message}`)
  252. };
  253. inbound_socket.on('error', onClose);
  254. inbound_socket.on('close', onClose);
  255. socks.connect({
  256. host: hostname,
  257. port: port,
  258. proxyHost: '127.0.0.1',
  259. proxyPort: socks_port,
  260. localDNS: false,
  261. auths: [ socks.auth.None() ]
  262. }, ($outbound_socket) => {
  263. this.emit('instance_connection', tor_instance, source);
  264. this.logger &amp;&amp; this.logger.verbose(`[http-connect]: ${source.hostname}:${source.port} → 127.0.0.1:${socks_port}${tor_instance.definition.Name ? ' ('+tor_instance.definition.Name+')' : '' } → ${hostname}:${port}`)
  265. outbound_socket = $outbound_socket;
  266. outbound_socket.on('close', onClose);
  267. outbound_socket.on('error', onClose);
  268. inbound_socket.write(`HTTP/1.1 200 Connection Established\r\n'+'Proxy-agent: ${TOR_ROUTER_PROXY_AGENT}\r\n` +'\r\n');
  269. outbound_socket.write(head);
  270. outbound_socket.pipe(inbound_socket);
  271. inbound_socket.pipe(outbound_socket);
  272. });
  273. };
  274. if (instance) {
  275. if (instance.ready) {
  276. connect(instance);
  277. }
  278. else {
  279. this.logger.debug(`[http-connect]: a connection has been attempted to "${instance.instance_name}", but it is not live... waiting for the instance to come online`);
  280. instance.once('ready', (() => connect(instance)));
  281. }
  282. }
  283. else if (this.tor_pool.instances.length) {
  284. connect(this.tor_pool.next());
  285. } else {
  286. this.logger.debug(`[http-connect]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online`);
  287. this.tor_pool.once('instance_created', connect);
  288. }
  289. }
  290. super(handle_http_connections);
  291. this.on('connect', handle_connect_connections);
  292. /**
  293. * Winston logger.
  294. * @type {Logger}
  295. * @public
  296. */
  297. this.logger = logger || require('./winston_silent_logger');
  298. /**
  299. * The pool of instances that will be used for requests.
  300. * @type {TorPool}
  301. * @public
  302. */
  303. this.tor_pool = tor_pool;
  304. /**
  305. * Configuration for "proxy by name" feature.
  306. * @type {ProxyByNameConfig}
  307. * @public
  308. */
  309. this.proxy_by_name = proxy_by_name;
  310. }
  311. };
  312. /**
  313. * Module that contains the {@link HTTPServer} class.
  314. * @module tor-router/HTTPServer
  315. * @see HTTPServer
  316. */
  317. module.exports = HTTPServer;</code></pre>
  318. </article>
  319. </section>
  320. </div>
  321. <br class="clear">
  322. <footer>
  323. Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Tue Sep 25 2018 12:53:23 GMT-0400 (Eastern Daylight Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
  324. </footer>
  325. <script>prettyPrint();</script>
  326. <script src="scripts/linenumber.js"></script>
  327. </body>
  328. </html>