| 
									
										
										
										
											2017-04-27 16:05:34 -06:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-10 16:05:54 -06:00
										 |  |  | module.exports.create = function (deps, conf, greenlockMiddleware) { | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |   var PromiseA = require('bluebird'); | 
					
						
							| 
									
										
										
										
											2017-05-18 14:09:02 -06:00
										 |  |  |   var statAsync = PromiseA.promisify(require('fs').stat); | 
					
						
							| 
									
										
										
										
											2017-05-16 13:04:08 -06:00
										 |  |  |   var domainMatches = require('../domain-utils').match; | 
					
						
							|  |  |  |   var separatePort = require('../domain-utils').separatePort; | 
					
						
							| 
									
										
										
										
											2017-05-09 14:16:21 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |   function parseHeaders(conn, opts) { | 
					
						
							|  |  |  |     // There should already be a `firstChunk` on the opts, but because we might sometimes
 | 
					
						
							|  |  |  |     // need more than that to get all the headers it's easier to always read the data off
 | 
					
						
							|  |  |  |     // the connection and put it back later if we need to.
 | 
					
						
							|  |  |  |     opts.firstChunk = Buffer.alloc(0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // First we make sure we have all of the headers.
 | 
					
						
							|  |  |  |     return new PromiseA(function (resolve, reject) { | 
					
						
							|  |  |  |       if (opts.firstChunk.includes('\r\n\r\n')) { | 
					
						
							|  |  |  |         resolve(opts.firstChunk.toString()); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       var errored = false; | 
					
						
							|  |  |  |       function handleErr(err) { | 
					
						
							|  |  |  |         errored = true; | 
					
						
							|  |  |  |         reject(err); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       conn.once('error', handleErr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       function handleChunk(chunk) { | 
					
						
							|  |  |  |         if (!errored) { | 
					
						
							|  |  |  |           opts.firstChunk = Buffer.concat([opts.firstChunk, chunk]); | 
					
						
							| 
									
										
										
										
											2017-09-15 18:25:23 -06:00
										 |  |  |           if (!opts.firstChunk.includes('\r\n\r\n')) { | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |             conn.once('data', handleChunk); | 
					
						
							| 
									
										
										
										
											2017-09-15 18:25:23 -06:00
										 |  |  |             return; | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2017-09-15 18:25:23 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |           conn.removeListener('error', handleErr); | 
					
						
							|  |  |  |           conn.pause(); | 
					
						
							|  |  |  |           resolve(opts.firstChunk.toString()); | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       conn.once('data', handleChunk); | 
					
						
							|  |  |  |     }).then(function (firstStr) { | 
					
						
							|  |  |  |       var headerSection = firstStr.split('\r\n\r\n')[0]; | 
					
						
							|  |  |  |       var lines = headerSection.split('\r\n'); | 
					
						
							|  |  |  |       var result = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       lines.slice(1).forEach(function (line) { | 
					
						
							| 
									
										
										
										
											2017-05-31 15:56:28 -06:00
										 |  |  |         var match = /([^:]*?)\s*:\s*(.*)/.exec(line); | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |         if (match) { | 
					
						
							|  |  |  |           result[match[1].toLowerCase()] = match[2]; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           console.error('HTTP header line does not match pattern', line); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       var match = /^([a-zA-Z]+)\s+(\S+)\s+HTTP/.exec(lines[0]); | 
					
						
							|  |  |  |       if (!match) { | 
					
						
							|  |  |  |         throw new Error('first line of "HTTP" does not match pattern: '+lines[0]); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       result.method = match[1].toUpperCase(); | 
					
						
							|  |  |  |       result.url = match[2]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return result; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-11 12:11:20 -06:00
										 |  |  |   function hostMatchesDomains(req, domainList) { | 
					
						
							| 
									
										
										
										
											2017-06-26 11:34:42 -06:00
										 |  |  |     var host = separatePort((req.headers || req).host).host.toLowerCase(); | 
					
						
							| 
									
										
										
										
											2017-05-16 13:04:08 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-11 12:11:20 -06:00
										 |  |  |     return domainList.some(function (pattern) { | 
					
						
							| 
									
										
										
										
											2017-05-16 13:04:08 -06:00
										 |  |  |       return domainMatches(pattern, host); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-23 18:26:03 -06:00
										 |  |  |   function determinePrimaryHost() { | 
					
						
							|  |  |  |     var result; | 
					
						
							| 
									
										
										
										
											2017-10-11 12:11:20 -06:00
										 |  |  |     if (Array.isArray(conf.domains)) { | 
					
						
							|  |  |  |       conf.domains.some(function (dom) { | 
					
						
							|  |  |  |         if (!dom.modules || !dom.modules.http) { | 
					
						
							|  |  |  |           return false; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-05-23 18:26:03 -06:00
										 |  |  |         return dom.names.some(function (domain) { | 
					
						
							|  |  |  |           if (domain[0] !== '*') { | 
					
						
							|  |  |  |             result = domain; | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (result) { | 
					
						
							|  |  |  |       return result; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (Array.isArray(conf.http.modules)) { | 
					
						
							|  |  |  |       conf.http.modules.some(function (mod) { | 
					
						
							|  |  |  |         return mod.domains.some(function (domain) { | 
					
						
							|  |  |  |           if (domain[0] !== '*') { | 
					
						
							|  |  |  |             result = domain; | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |   // We handle both HTTPS and HTTP traffic on the same ports, and we want to redirect
 | 
					
						
							|  |  |  |   // any unencrypted requests to the same port they came from unless it came in on
 | 
					
						
							|  |  |  |   // the default HTTP port, in which case there wont be a port specified in the host.
 | 
					
						
							|  |  |  |   var redirecters = {}; | 
					
						
							|  |  |  |   var ipv4Re = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; | 
					
						
							|  |  |  |   var ipv6Re = /^\[[0-9a-fA-F:]+\]$/; | 
					
						
							|  |  |  |   function redirectHttps(req, res) { | 
					
						
							|  |  |  |     var host = separatePort(req.headers.host); | 
					
						
							| 
									
										
										
										
											2017-05-11 19:16:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |     if (!redirecters[host.port]) { | 
					
						
							|  |  |  |       redirecters[host.port] = require('redirect-https')({ port: host.port }); | 
					
						
							| 
									
										
										
										
											2017-05-11 19:16:23 -06:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |     // localhost and IP addresses cannot have real SSL certs (and don't contain any useful
 | 
					
						
							|  |  |  |     // info for redirection either), so we direct some hosts to either localhost.daplie.me
 | 
					
						
							|  |  |  |     // or the "primary domain" ie the first manually specified domain.
 | 
					
						
							|  |  |  |     if (host.host === 'localhost') { | 
					
						
							|  |  |  |       req.headers.host = 'localhost.daplie.me' + (host.port ? ':'+host.port : ''); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-05-11 19:16:23 -06:00
										 |  |  |     // Test for IPv4 and IPv6 addresses. These patterns will match some invalid addresses,
 | 
					
						
							|  |  |  |     // but since those still won't be valid domains that won't really be a problem.
 | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |     if (ipv4Re.test(host.host) || ipv6Re.test(host.host)) { | 
					
						
							| 
									
										
										
										
											2017-05-23 18:26:03 -06:00
										 |  |  |       var dest; | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |       if (conf.http.primaryDomain) { | 
					
						
							| 
									
										
										
										
											2017-05-23 18:26:03 -06:00
										 |  |  |         dest = conf.http.primaryDomain; | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         dest = determinePrimaryHost(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (dest) { | 
					
						
							|  |  |  |         req.headers.host = dest + (host.port ? ':'+host.port : ''); | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2017-05-11 19:16:23 -06:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |     redirecters[host.port](req, res); | 
					
						
							| 
									
										
										
										
											2017-05-09 15:46:49 -06:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |   function emitConnection(server, conn, opts) { | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |     server.emit('connection', conn); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // We need to put back whatever data we read off to determine the connection was HTTP
 | 
					
						
							|  |  |  |     // and to parse the headers. Must be done after data handlers added but before any new
 | 
					
						
							|  |  |  |     // data comes in.
 | 
					
						
							|  |  |  |     process.nextTick(function () { | 
					
						
							|  |  |  |       conn.unshift(opts.firstChunk); | 
					
						
							| 
									
										
										
										
											2017-09-15 18:25:23 -06:00
										 |  |  |       conn.resume(); | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2017-05-16 02:20:02 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |     // Convenience return for all the check* functions.
 | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |   var acmeServer; | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  |   function checkAcme(conn, opts, headers) { | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |     if (headers.url.indexOf('/.well-known/acme-challenge/') !== 0) { | 
					
						
							|  |  |  |       return false; | 
					
						
							| 
									
										
										
										
											2017-05-16 02:20:02 -05:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  |     if (deps.tunnelServer.isClientDomain(separatePort(headers.host).host)) { | 
					
						
							|  |  |  |       deps.tunnelServer.handleClientConn(conn); | 
					
						
							|  |  |  |       process.nextTick(function () { | 
					
						
							|  |  |  |         conn.unshift(opts.firstChunk); | 
					
						
							| 
									
										
										
										
											2017-09-15 18:25:23 -06:00
										 |  |  |         conn.resume(); | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  |       }); | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |     if (!acmeServer) { | 
					
						
							|  |  |  |       acmeServer = require('http').createServer(greenlockMiddleware); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return emitConnection(acmeServer, conn, opts); | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-26 11:34:42 -06:00
										 |  |  |   function checkLoopback(conn, opts, headers) { | 
					
						
							|  |  |  |     if (headers.url.indexOf('/.well-known/cloud-challenge/') !== 0) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return emitConnection(deps.loopback.server, conn, opts); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |   var httpsRedirectServer; | 
					
						
							|  |  |  |   function checkHttps(conn, opts, headers) { | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |     if (conf.http.allowInsecure || conn.encrypted) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (conf.http.trustProxy && 'https' === headers['x-forwarded-proto']) { | 
					
						
							|  |  |  |       return false; | 
					
						
							| 
									
										
										
										
											2017-05-16 02:20:02 -05:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |     if (!httpsRedirectServer) { | 
					
						
							|  |  |  |       httpsRedirectServer = require('http').createServer(redirectHttps); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return emitConnection(httpsRedirectServer, conn, opts); | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  |   var adminDomains; | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |   var adminServer; | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |   function checkAdmin(conn, opts, headers) { | 
					
						
							| 
									
										
										
										
											2017-05-31 15:56:28 -06:00
										 |  |  |     var host = separatePort(headers.host).host; | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  |     if (!adminDomains) { | 
					
						
							| 
									
										
										
										
											2017-10-03 17:26:44 -06:00
										 |  |  |       adminDomains = require('../admin').adminDomains; | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  |     } | 
					
						
							|  |  |  |     if (adminDomains.indexOf(host) !== -1) { | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |       if (!adminServer) { | 
					
						
							| 
									
										
										
										
											2017-10-03 17:26:44 -06:00
										 |  |  |         adminServer = require('../admin').create(deps, conf); | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |       } | 
					
						
							|  |  |  |       return emitConnection(adminServer, conn, opts); | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (deps.tunnelServer.isAdminDomain(host)) { | 
					
						
							|  |  |  |       deps.tunnelServer.handleAdminConn(conn); | 
					
						
							|  |  |  |       process.nextTick(function () { | 
					
						
							|  |  |  |         conn.unshift(opts.firstChunk); | 
					
						
							| 
									
										
										
										
											2017-09-15 18:25:23 -06:00
										 |  |  |         conn.resume(); | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  |       }); | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-02 18:10:16 -06:00
										 |  |  |   var proxyServer; | 
					
						
							|  |  |  |   function createProxyServer() { | 
					
						
							|  |  |  |     var http = require('http'); | 
					
						
							|  |  |  |     var agent = new http.Agent(); | 
					
						
							|  |  |  |     agent.createConnection = deps.net.createConnection; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var proxy = require('http-proxy').createProxyServer({ | 
					
						
							| 
									
										
										
										
											2017-06-08 10:44:22 -06:00
										 |  |  |       agent: agent | 
					
						
							|  |  |  |     , toProxy: true | 
					
						
							| 
									
										
										
										
											2017-06-02 18:10:16 -06:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-09 11:58:43 -06:00
										 |  |  |     proxy.on('error', function (err, req, res) { | 
					
						
							|  |  |  |       res.statusCode = 502; | 
					
						
							|  |  |  |       res.setHeader('Connection', 'close'); | 
					
						
							|  |  |  |       res.setHeader('Content-Type', 'text/html'); | 
					
						
							|  |  |  |       res.end(require('../proxy-conn').getRespBody(err, conf.debug)); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-02 18:10:16 -06:00
										 |  |  |     proxyServer = http.createServer(function (req, res) { | 
					
						
							|  |  |  |       proxy.web(req, res, req.connection.proxyOpts); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     proxyServer.on('upgrade', function (req, socket, head) { | 
					
						
							|  |  |  |       proxy.ws(req, socket, head, socket.proxyOpts); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-06-16 17:51:03 -06:00
										 |  |  |   function proxyRequest(mod, conn, opts, xHeaders) { | 
					
						
							| 
									
										
										
										
											2017-06-02 18:10:16 -06:00
										 |  |  |     if (!proxyServer) { | 
					
						
							|  |  |  |       createProxyServer(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     conn.proxyOpts = { | 
					
						
							| 
									
										
										
										
											2017-06-08 10:44:22 -06:00
										 |  |  |       target: 'http://'+(mod.address || (mod.host || 'localhost')+':'+mod.port) | 
					
						
							|  |  |  |     , headers: xHeaders | 
					
						
							| 
									
										
										
										
											2017-06-02 18:10:16 -06:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2017-06-16 17:51:03 -06:00
										 |  |  |     return emitConnection(proxyServer, conn, opts); | 
					
						
							| 
									
										
										
										
											2017-06-02 18:10:16 -06:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-16 17:51:03 -06:00
										 |  |  |   function proxyWebsocket(mod, conn, opts, headers, xHeaders) { | 
					
						
							| 
									
										
										
										
											2017-05-17 18:43:44 -06:00
										 |  |  |     var index = opts.firstChunk.indexOf('\r\n\r\n'); | 
					
						
							|  |  |  |     var body = opts.firstChunk.slice(index); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var head = opts.firstChunk.slice(0, index).toString(); | 
					
						
							|  |  |  |     var headLines = head.split('\r\n'); | 
					
						
							|  |  |  |     // First strip any existing `X-Forwarded-*` headers (for security purposes?)
 | 
					
						
							|  |  |  |     headLines = headLines.filter(function (line) { | 
					
						
							|  |  |  |       return !/^x-forwarded/i.test(line); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     // Then add our own `X-Forwarded` headers at the end.
 | 
					
						
							| 
									
										
										
										
											2017-06-16 17:51:03 -06:00
										 |  |  |     Object.keys(xHeaders).forEach(function (key) { | 
					
						
							|  |  |  |       headLines.push(key + ': ' +xHeaders[key]); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2017-05-17 18:43:44 -06:00
										 |  |  |     // Then convert all of the head lines back into a header buffer.
 | 
					
						
							|  |  |  |     head = Buffer.from(headLines.join('\r\n')); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     opts.firstChunk = Buffer.concat([head, body]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-24 13:05:37 -06:00
										 |  |  |     var newConnOpts = separatePort(mod.address || ''); | 
					
						
							|  |  |  |     newConnOpts.port = newConnOpts.port || mod.port; | 
					
						
							|  |  |  |     newConnOpts.host = newConnOpts.host || mod.host || 'localhost'; | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |     newConnOpts.servername = separatePort(headers.host).host; | 
					
						
							|  |  |  |     newConnOpts.data = opts.firstChunk; | 
					
						
							| 
									
										
										
										
											2017-05-16 02:20:02 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |     newConnOpts.remoteFamily  = opts.family  || conn.remoteFamily; | 
					
						
							|  |  |  |     newConnOpts.remoteAddress = opts.address || conn.remoteAddress; | 
					
						
							|  |  |  |     newConnOpts.remotePort    = opts.port    || conn.remotePort; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-17 14:06:24 -06:00
										 |  |  |     deps.proxy(conn, newConnOpts, opts.firstChunk); | 
					
						
							| 
									
										
										
										
											2017-06-02 18:10:16 -06:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function checkProxy(mod, conn, opts, headers) { | 
					
						
							| 
									
										
										
										
											2017-06-16 17:51:03 -06:00
										 |  |  |     var xHeaders = {}; | 
					
						
							|  |  |  |     // Then add our own `X-Forwarded` headers at the end.
 | 
					
						
							|  |  |  |     if (conf.http.trustProxy && headers['x-forwarded-proto']) { | 
					
						
							|  |  |  |       xHeaders['X-Forwarded-Proto'] = headers['x-forwarded-proto']; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       xHeaders['X-Forwarded-Proto'] = conn.encrypted ? 'https' : 'http'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     var proxyChain = (headers['x-forwarded-for'] || '').split(/ *, */).filter(Boolean); | 
					
						
							|  |  |  |     proxyChain.push(opts.remoteAddress || opts.address || conn.remoteAddress); | 
					
						
							|  |  |  |     xHeaders['X-Forwarded-For'] = proxyChain.join(', '); | 
					
						
							|  |  |  |     xHeaders['X-Forwarded-Host'] = headers.host; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-02 18:10:16 -06:00
										 |  |  |     if ((headers.connection || '').toLowerCase() === 'upgrade') { | 
					
						
							| 
									
										
										
										
											2017-06-16 17:51:03 -06:00
										 |  |  |       proxyWebsocket(mod, conn, opts, headers, xHeaders); | 
					
						
							| 
									
										
										
										
											2017-06-02 18:10:16 -06:00
										 |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2017-06-16 17:51:03 -06:00
										 |  |  |       proxyRequest(mod, conn, opts, xHeaders); | 
					
						
							| 
									
										
										
										
											2017-06-02 18:10:16 -06:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-18 11:58:10 -06:00
										 |  |  |   function checkRedirect(mod, conn, opts, headers) { | 
					
						
							|  |  |  |     if (!mod.fromRe || mod.fromRe.origSrc !== mod.from) { | 
					
						
							|  |  |  |       // Escape any characters that (can) have special meaning in regular expression
 | 
					
						
							|  |  |  |       // but that aren't the special characters we have interest in.
 | 
					
						
							|  |  |  |       var from = mod.from.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&'); | 
					
						
							|  |  |  |       // Then modify the characters we are interested in so they do what we want in
 | 
					
						
							|  |  |  |       // the regular expression after being compiled.
 | 
					
						
							|  |  |  |       from = from.replace(/\*/g, '(.*)'); | 
					
						
							|  |  |  |       var fromRe = new RegExp('^' + from + '/?$'); | 
					
						
							|  |  |  |       fromRe.origSrc = mod.from; | 
					
						
							|  |  |  |       // We don't want this property showing up in the actual config file or the API,
 | 
					
						
							|  |  |  |       // so we define it this way so it's not enumberable.
 | 
					
						
							|  |  |  |       Object.defineProperty(mod, 'fromRe', {value: fromRe, configurable: true}); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var match = mod.fromRe.exec(headers.url); | 
					
						
							|  |  |  |     if (!match) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var to = mod.to; | 
					
						
							|  |  |  |     match.slice(1).forEach(function (globMatch, index) { | 
					
						
							|  |  |  |       to = to.replace(':'+(index+1), globMatch); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     var status = mod.status || 301; | 
					
						
							|  |  |  |     var code = require('http').STATUS_CODES[status] || 'Unknown'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     conn.end([ | 
					
						
							|  |  |  |       'HTTP/1.1 ' + status + ' ' + code | 
					
						
							|  |  |  |     , 'Date: ' + (new Date()).toUTCString() | 
					
						
							|  |  |  |     , 'Location: ' + to | 
					
						
							| 
									
										
										
										
											2017-05-18 14:09:02 -06:00
										 |  |  |     , 'Connection: close' | 
					
						
							| 
									
										
										
										
											2017-05-18 11:58:10 -06:00
										 |  |  |     , 'Content-Length: 0' | 
					
						
							|  |  |  |     , '' | 
					
						
							|  |  |  |     , '' | 
					
						
							|  |  |  |     ].join('\r\n')); | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-18 14:09:02 -06:00
										 |  |  |   var staticServer; | 
					
						
							|  |  |  |   var staticHandlers = {}; | 
					
						
							|  |  |  |   function serveStatic(req, res) { | 
					
						
							|  |  |  |     var rootDir = req.connection.rootDir; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!staticHandlers[rootDir]) { | 
					
						
							|  |  |  |       staticHandlers[rootDir] = require('express').static(rootDir, { fallthrough: false }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     staticHandlers[rootDir](req, res, function (err) { | 
					
						
							|  |  |  |       if (err) { | 
					
						
							|  |  |  |         res.statusCode = err.statusCode; | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         res.statusCode = 404; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       res.setHeader('Content-Type', 'text/html'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (res.statusCode === 404) { | 
					
						
							|  |  |  |         res.end('File Not Found'); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         res.end(require('http').STATUS_CODES[res.statusCode]); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   function checkStatic(mod, conn, opts, headers) { | 
					
						
							|  |  |  |     var rootDir = mod.root.replace(':hostname', separatePort(headers.host).host); | 
					
						
							|  |  |  |     return statAsync(rootDir) | 
					
						
							|  |  |  |       .then(function (stats) { | 
					
						
							|  |  |  |         if (!stats || !stats.isDirectory()) { | 
					
						
							|  |  |  |           return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!staticServer) { | 
					
						
							|  |  |  |           staticServer = require('http').createServer(serveStatic); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         conn.rootDir = rootDir; | 
					
						
							|  |  |  |         return emitConnection(staticServer, conn, opts); | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       .catch(function (err) { | 
					
						
							|  |  |  |         if (err.code !== 'ENOENT') { | 
					
						
							|  |  |  |           console.warn('errored stating', rootDir, 'for serving static files', err); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       ; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-23 18:26:03 -06:00
										 |  |  |   var moduleChecks = { | 
					
						
							|  |  |  |     proxy:    checkProxy | 
					
						
							|  |  |  |   , redirect: checkRedirect | 
					
						
							|  |  |  |   , static:   checkStatic | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |   function handleConnection(conn) { | 
					
						
							|  |  |  |     var opts = conn.__opts; | 
					
						
							|  |  |  |     parseHeaders(conn, opts) | 
					
						
							|  |  |  |       .then(function (headers) { | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  |         if (checkAcme(conn, opts, headers))  { return; } | 
					
						
							| 
									
										
										
										
											2017-06-26 11:34:42 -06:00
										 |  |  |         if (checkLoopback(conn, opts, headers))  { return; } | 
					
						
							| 
									
										
										
										
											2017-05-17 19:16:45 -06:00
										 |  |  |         if (checkHttps(conn, opts, headers)) { return; } | 
					
						
							|  |  |  |         if (checkAdmin(conn, opts, headers)) { return; } | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-18 14:09:02 -06:00
										 |  |  |         var prom = PromiseA.resolve(false); | 
					
						
							| 
									
										
										
										
											2017-10-11 12:11:20 -06:00
										 |  |  |         (conf.domains || []).forEach(function (dom) { | 
					
						
							| 
									
										
										
										
											2017-05-18 14:09:02 -06:00
										 |  |  |           prom = prom.then(function (handled) { | 
					
						
							|  |  |  |             if (handled) { | 
					
						
							|  |  |  |               return handled; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2017-10-11 12:11:20 -06:00
										 |  |  |             if (!dom.modules || !dom.modules.http) { | 
					
						
							|  |  |  |               return false; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2017-05-23 18:26:03 -06:00
										 |  |  |             if (!hostMatchesDomains(headers, dom.names)) { | 
					
						
							|  |  |  |               return false; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-04 16:42:10 -06:00
										 |  |  |             var subProm = PromiseA.resolve(false); | 
					
						
							| 
									
										
										
										
											2017-10-11 12:11:20 -06:00
										 |  |  |             dom.modules.http.forEach(function (mod) { | 
					
						
							| 
									
										
										
										
											2017-10-05 18:11:58 -06:00
										 |  |  |               if (moduleChecks[mod.type]) { | 
					
						
							| 
									
										
										
										
											2017-08-04 16:42:10 -06:00
										 |  |  |                 subProm = subProm.then(function (handled) { | 
					
						
							|  |  |  |                   if (handled) { return handled; } | 
					
						
							| 
									
										
										
										
											2017-10-05 18:11:58 -06:00
										 |  |  |                   return moduleChecks[mod.type](mod, conn, opts, headers); | 
					
						
							| 
									
										
										
										
											2017-08-04 16:42:10 -06:00
										 |  |  |                 }); | 
					
						
							| 
									
										
										
										
											2017-09-11 15:57:25 -06:00
										 |  |  |               } else { | 
					
						
							|  |  |  |                 console.warn('unknown HTTP module under domains', dom.names.join(','), mod); | 
					
						
							| 
									
										
										
										
											2017-05-23 18:26:03 -06:00
										 |  |  |               } | 
					
						
							|  |  |  |             }); | 
					
						
							| 
									
										
										
										
											2017-08-04 16:42:10 -06:00
										 |  |  |             return subProm; | 
					
						
							| 
									
										
										
										
											2017-05-23 18:26:03 -06:00
										 |  |  |           }); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         (conf.http.modules || []).forEach(function (mod) { | 
					
						
							|  |  |  |           prom = prom.then(function (handled) { | 
					
						
							|  |  |  |             if (handled) { | 
					
						
							|  |  |  |               return handled; | 
					
						
							| 
									
										
										
										
											2017-05-18 14:09:02 -06:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2017-05-23 18:26:03 -06:00
										 |  |  |             if (!hostMatchesDomains(headers, mod.domains)) { | 
					
						
							|  |  |  |               return false; | 
					
						
							| 
									
										
										
										
											2017-05-18 14:09:02 -06:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2017-05-23 18:26:03 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-05 18:11:58 -06:00
										 |  |  |             if (moduleChecks[mod.type]) { | 
					
						
							|  |  |  |               return moduleChecks[mod.type](mod, conn, opts, headers); | 
					
						
							| 
									
										
										
										
											2017-05-18 14:09:02 -06:00
										 |  |  |             } | 
					
						
							|  |  |  |             console.warn('unknown HTTP module found', mod); | 
					
						
							|  |  |  |           }); | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-03 15:56:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-18 14:09:02 -06:00
										 |  |  |         prom.then(function (handled) { | 
					
						
							| 
									
										
										
										
											2017-08-03 15:56:19 -06:00
										 |  |  |           // XXX TODO SECURITY html escape
 | 
					
						
							| 
									
										
										
										
											2017-08-04 14:38:22 -06:00
										 |  |  |           var host = (headers.host || '[no host header]').replace(/</, '<'); | 
					
						
							|  |  |  |           // TODO specify filepath of config file or database connection, etc
 | 
					
						
							| 
									
										
										
										
											2017-08-03 15:56:19 -06:00
										 |  |  |           var msg = "Bad Gateway: Goldilocks accepted '" + host + "' but no module (neither static nor proxy) was designated to handle it. Check your config file."; | 
					
						
							| 
									
										
										
										
											2017-05-18 14:09:02 -06:00
										 |  |  |           if (!handled) { | 
					
						
							|  |  |  |             conn.end([ | 
					
						
							| 
									
										
										
										
											2017-08-03 15:56:19 -06:00
										 |  |  |               'HTTP/1.1 502 Bad Gateway' | 
					
						
							| 
									
										
										
										
											2017-05-18 14:09:02 -06:00
										 |  |  |             , 'Date: ' + (new Date()).toUTCString() | 
					
						
							|  |  |  |             , 'Content-Type: text/html' | 
					
						
							| 
									
										
										
										
											2017-08-03 15:56:19 -06:00
										 |  |  |             , 'Content-Length: ' + msg.length | 
					
						
							| 
									
										
										
										
											2017-05-18 14:09:02 -06:00
										 |  |  |             , 'Connection: close' | 
					
						
							|  |  |  |             , '' | 
					
						
							| 
									
										
										
										
											2017-08-03 15:56:19 -06:00
										 |  |  |             , msg | 
					
						
							| 
									
										
										
										
											2017-05-18 14:09:02 -06:00
										 |  |  |             ].join('\r\n')); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2017-05-16 17:19:26 -06:00
										 |  |  |       }) | 
					
						
							|  |  |  |       ; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     emit: function (type, value) { | 
					
						
							|  |  |  |       if (type === 'connection') { | 
					
						
							|  |  |  |         handleConnection(value); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2017-04-27 16:05:34 -06:00
										 |  |  | }; |