| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var HttpMiddleware = module.exports; | 
					
						
							|  |  |  | var servernameRe = /^[a-z0-9\.\-]+$/i; | 
					
						
							|  |  |  | var challengePrefix = "/.well-known/acme-challenge/"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | HttpMiddleware.create = function(gl, defaultApp) { | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |     if (defaultApp && "function" !== typeof defaultApp) { | 
					
						
							|  |  |  |         throw new Error("use greenlock.httpMiddleware() or greenlock.httpMiddleware(function (req, res) {})"); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |     return function(req, res, next) { | 
					
						
							|  |  |  |         var hostname = HttpMiddleware.sanitizeHostname(req); | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |         req.on("error", function(err) { | 
					
						
							|  |  |  |             explainError(gl, err, "http_01_middleware_socket", hostname); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 20:39:59 -06:00
										 |  |  |         // Skip unless the path begins with /.well-known/acme-challenge/
 | 
					
						
							|  |  |  |         if (!hostname || 0 !== req.url.indexOf(challengePrefix)) { | 
					
						
							|  |  |  |             skipChallenge(req, res, next, defaultApp); | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 20:39:59 -06:00
										 |  |  |         // HEADERS SENT DEBUG NOTE #2
 | 
					
						
							|  |  |  |         // at this point, it's most likely Let's Encrypt server
 | 
					
						
							|  |  |  |         // (or greenlock itself) performing the verification process
 | 
					
						
							|  |  |  |         // Hmmm... perhaps we should change the greenlock prefix to test
 | 
					
						
							|  |  |  |         // Anyway, we just got fast the first place where we could
 | 
					
						
							|  |  |  |         // be sending headers.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |         var token = req.url.slice(challengePrefix.length); | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 20:39:59 -06:00
										 |  |  |         var done = false; | 
					
						
							|  |  |  |         var countA = 0; | 
					
						
							|  |  |  |         var countB = 0; | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |         gl.getAcmeHttp01ChallengeResponse({ type: "http-01", servername: hostname, token: token }) | 
					
						
							|  |  |  |             .catch(function(err) { | 
					
						
							| 
									
										
										
										
											2019-11-02 20:39:59 -06:00
										 |  |  |                 countA += 1; | 
					
						
							|  |  |  |                 // HEADERS SENT DEBUG NOTE #3
 | 
					
						
							|  |  |  |                 // This is the second possible time we could be sending headers
 | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |                 respondToError(gl, res, err, "http_01_middleware_challenge_response", hostname); | 
					
						
							| 
									
										
										
										
											2019-11-02 20:39:59 -06:00
										 |  |  |                 done = true; | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |                 return { __done: true }; | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             .then(function(result) { | 
					
						
							| 
									
										
										
										
											2019-11-02 20:39:59 -06:00
										 |  |  |                 countB += 1; | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |                 if (result && result.__done) { | 
					
						
							|  |  |  |                     return; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2019-11-02 20:39:59 -06:00
										 |  |  |                 if (done) { | 
					
						
							|  |  |  |                     console.error("Sanity check fail: `done` is in a quantum state of both true and false... huh?"); | 
					
						
							|  |  |  |                     return; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 // HEADERS SENT DEBUG NOTE #4b
 | 
					
						
							|  |  |  |                 // This is the third/fourth possible time send headers
 | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |                 return respondWithGrace(res, result, hostname, token); | 
					
						
							| 
									
										
										
										
											2019-11-02 20:39:59 -06:00
										 |  |  |             }) | 
					
						
							|  |  |  |             .catch(function(err) { | 
					
						
							|  |  |  |                 // HEADERS SENT DEBUG NOTE #5
 | 
					
						
							|  |  |  |                 // I really don't see how this can be possible.
 | 
					
						
							|  |  |  |                 // Every case appears to be accounted for
 | 
					
						
							|  |  |  |                 console.error(); | 
					
						
							|  |  |  |                 console.error("[warning] Developer Error:" + (err.code || err.context || ""), countA, countB); | 
					
						
							|  |  |  |                 console.error(err.stack); | 
					
						
							|  |  |  |                 console.error(); | 
					
						
							|  |  |  |                 console.error( | 
					
						
							|  |  |  |                     "This is probably the error that happens routinely on http2 connections, but we're not sure why." | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  |                 console.error("To track the status or help contribute,"); | 
					
						
							|  |  |  |                 console.error("visit: https://git.rootprojects.org/root/greenlock-express.js/issues/9"); | 
					
						
							|  |  |  |                 console.error(); | 
					
						
							|  |  |  |                 try { | 
					
						
							|  |  |  |                     res.end("Internal Server Error [1003]: See logs for details."); | 
					
						
							|  |  |  |                 } catch (e) { | 
					
						
							|  |  |  |                     // ignore
 | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |             }); | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-02 20:39:59 -06:00
										 |  |  | function skipChallenge(req, res, next, defaultApp) { | 
					
						
							|  |  |  |     if ("function" === typeof defaultApp) { | 
					
						
							|  |  |  |         defaultApp(req, res, next); | 
					
						
							|  |  |  |     } else if ("function" === typeof next) { | 
					
						
							|  |  |  |         next(); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         res.statusCode = 500; | 
					
						
							|  |  |  |         res.end("[500] Developer Error: app.use('/', greenlock.httpMiddleware()) or greenlock.httpMiddleware(app)"); | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function respondWithGrace(res, result, hostname, token) { | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |     var keyAuth = result && result.keyAuthorization; | 
					
						
							| 
									
										
										
										
											2019-11-02 20:39:59 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // HEADERS SENT DEBUG NOTE #4b
 | 
					
						
							|  |  |  |     // This is (still) the third/fourth possible time we could be sending headers
 | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |     if (keyAuth && "string" === typeof keyAuth) { | 
					
						
							|  |  |  |         res.setHeader("Content-Type", "text/plain; charset=utf-8"); | 
					
						
							|  |  |  |         res.end(keyAuth); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |     res.statusCode = 404; | 
					
						
							|  |  |  |     res.setHeader("Content-Type", "application/json; charset=utf-8"); | 
					
						
							|  |  |  |     res.end(JSON.stringify({ error: { message: "domain '" + hostname + "' has no token '" + token + "'." } })); | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function explainError(gl, err, ctx, hostname) { | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |     if (!err.servername) { | 
					
						
							|  |  |  |         err.servername = hostname; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!err.context) { | 
					
						
							|  |  |  |         err.context = ctx; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-11-02 20:39:59 -06:00
										 |  |  |     // leaving this in the build for now because it will help with existing error reports
 | 
					
						
							|  |  |  |     console.error("[warning] network connection error:", (err.context || "") + " " + err.message); | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |     (gl.notify || gl._notify)("error", err); | 
					
						
							|  |  |  |     return err; | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function respondToError(gl, res, err, ctx, hostname) { | 
					
						
							| 
									
										
										
										
											2019-11-02 20:39:59 -06:00
										 |  |  |     // HEADERS SENT DEBUG NOTE #3b
 | 
					
						
							|  |  |  |     // This is (still) the second possible time we could be sending headers
 | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |     err = explainError(gl, err, ctx, hostname); | 
					
						
							|  |  |  |     res.statusCode = 500; | 
					
						
							| 
									
										
										
										
											2019-11-02 20:39:59 -06:00
										 |  |  |     res.end("Internal Server Error [1004]: See logs for details."); | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | HttpMiddleware.getHostname = function(req) { | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |     return req.hostname || req.headers["x-forwarded-host"] || (req.headers.host || ""); | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | }; | 
					
						
							|  |  |  | HttpMiddleware.sanitizeHostname = function(req) { | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |     // we can trust XFH because spoofing causes no ham in this limited use-case scenario
 | 
					
						
							|  |  |  |     // (and only telebit would be legitimately setting XFH)
 | 
					
						
							|  |  |  |     var servername = HttpMiddleware.getHostname(req) | 
					
						
							|  |  |  |         .toLowerCase() | 
					
						
							|  |  |  |         .replace(/:.*/, ""); | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |         req.hostname = servername; | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							|  |  |  |         // read-only express property
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (req.headers["x-forwarded-host"]) { | 
					
						
							|  |  |  |         req.headers["x-forwarded-host"] = servername; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |         req.headers.host = servername; | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							|  |  |  |         // TODO is this a possible error?
 | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |     return (servernameRe.test(servername) && -1 === servername.indexOf("..") && servername) || ""; | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | }; |