| 
									
										
										
										
											2016-04-09 19:14:00 -04:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports.create = function (app, xconfx, apiFactories, apiDeps) { | 
					
						
							|  |  |  |   var PromiseA = require('bluebird'); | 
					
						
							|  |  |  |   var path = require('path'); | 
					
						
							|  |  |  |   var fs = PromiseA.promisifyAll(require('fs')); | 
					
						
							|  |  |  |   // NOTE: each process has its own cache
 | 
					
						
							|  |  |  |   var localCache = { le: {}, statics: {} }; | 
					
						
							|  |  |  |   var express = require('express'); | 
					
						
							|  |  |  |   var apiApp; | 
					
						
							|  |  |  |   var setupDomain = xconfx.setupDomain = ('cloud.' + xconfx.primaryDomain); | 
					
						
							|  |  |  |   var setupApp; | 
					
						
							| 
									
										
										
										
											2016-06-08 14:05:10 -04:00
										 |  |  |   var CORS; | 
					
						
							|  |  |  |   var cors; | 
					
						
							| 
									
										
										
										
											2016-04-09 19:14:00 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-08 14:05:10 -04:00
										 |  |  |   function redirectSetup(reason, req, res/*, next*/) { | 
					
						
							| 
									
										
										
										
											2017-05-04 23:09:56 -06:00
										 |  |  |     console.log('xconfx', xconfx); | 
					
						
							| 
									
										
										
										
											2016-06-08 14:05:10 -04:00
										 |  |  |     var url = 'https://cloud.' + xconfx.primaryDomain; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (443 !== xconfx.externalPort) { | 
					
						
							|  |  |  |       url += ':' + xconfx.externalPort; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     url += '#referrer=' + reason; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     res.statusCode = 302; | 
					
						
							|  |  |  |     res.setHeader('Location', url); | 
					
						
							|  |  |  |     res.end(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-09 19:14:00 -04:00
										 |  |  |   function disallowSymLinks(req, res) { | 
					
						
							|  |  |  |     res.end( | 
					
						
							|  |  |  |       "Symbolic Links are not supported on all platforms and are therefore disallowed." | 
					
						
							|  |  |  |     + " Instead, simply create a file of the same name as the link with a single line of text" | 
					
						
							|  |  |  |     + " which should be the relative or absolute path to the target directory." | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function disallowNonFiles(req, res) { | 
					
						
							|  |  |  |     res.end( | 
					
						
							|  |  |  |       "Pipes, Blocks, Sockets, FIFOs, and other such nonsense are not permitted." | 
					
						
							|  |  |  |     + " Instead please create a directory from which to read or create a file " | 
					
						
							|  |  |  |     + " with a single line of text which should be the target directory to read from." | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function securityError(req, res) { | 
					
						
							|  |  |  |     res.end("Security Error: Link points outside of packages/pages"); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function notConfigured(req, res, next) { | 
					
						
							|  |  |  |     if (setupDomain !== req.hostname) { | 
					
						
							|  |  |  |       redirectSetup(req.hostname, req, res); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!setupApp) { | 
					
						
							|  |  |  |       setupApp = express.static(path.join(xconfx.staticpath, 'com.daplie.walnut')); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     setupApp(req, res, function () { | 
					
						
							|  |  |  |       if ('/' === req.url) { | 
					
						
							|  |  |  |         res.end('Sanity Fail: Configurator not found'); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       next(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function loadHandler(name) { | 
					
						
							|  |  |  |     return function handler(req, res, next) { | 
					
						
							|  |  |  |       var packagepath = path.join(xconfx.staticpath, name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return fs.lstatAsync(packagepath).then(function (stat) { | 
					
						
							|  |  |  |         if (stat.isSymbolicLink()) { | 
					
						
							|  |  |  |           return disallowSymLinks; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (stat.isDirectory()) { | 
					
						
							|  |  |  |           return express.static(packagepath); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!stat.isFile()) { | 
					
						
							|  |  |  |           return disallowNonFiles; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return fs.readFileAsync(packagepath, 'utf8').then(function (text) { | 
					
						
							|  |  |  |           // TODO allow cascading
 | 
					
						
							|  |  |  |           text = text.trim().split(/\n/)[0]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           // TODO rerun the above, disallowing link-style (or count or memoize to prevent infinite loop)
 | 
					
						
							|  |  |  |           // TODO make safe
 | 
					
						
							|  |  |  |           packagepath = path.resolve(xconfx.staticpath, text); | 
					
						
							|  |  |  |           if (0 !== packagepath.indexOf(xconfx.staticpath)) { | 
					
						
							|  |  |  |             return securityError; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return express.static(packagepath); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }, function (/*err*/) { | 
					
						
							|  |  |  |         return notConfigured; | 
					
						
							|  |  |  |       }).then(function (handler) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // keep object reference intact
 | 
					
						
							|  |  |  |         localCache.statics[name].handler = handler; | 
					
						
							|  |  |  |         handler(req, res, next); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function staticHelper(appId, opts) { | 
					
						
							|  |  |  |     // TODO inter-process cache expirey
 | 
					
						
							|  |  |  |     // TODO add to xconfx.staticpath
 | 
					
						
							|  |  |  |     xconfx.staticpath = path.join(__dirname, '..', '..', 'packages', 'pages'); | 
					
						
							|  |  |  |     return fs.readdirAsync(xconfx.staticpath).then(function (nodes) { | 
					
						
							|  |  |  |       if (opts && opts.clear) { | 
					
						
							|  |  |  |         localCache.statics = {}; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // longest to shortest
 | 
					
						
							|  |  |  |       function shortToLong(a, b) { | 
					
						
							|  |  |  |         return b.length - a.length; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       nodes.sort(shortToLong); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       nodes.forEach(function (name) { | 
					
						
							|  |  |  |         if (!localCache.statics[name]) { | 
					
						
							|  |  |  |           localCache.statics[name] = { handler: loadHandler(name), createdAt: Date.now() }; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Secure Matching
 | 
					
						
							|  |  |  |       // apple.com#blah#  apple.com#blah#
 | 
					
						
							|  |  |  |       // apple.com.us#    apple.com#foo#
 | 
					
						
							|  |  |  |       // apple.com#       apple.com#foo#
 | 
					
						
							|  |  |  |       nodes.some(function (name) { | 
					
						
							|  |  |  |         if (0 === (name + '#').indexOf(appId + '#')) { | 
					
						
							|  |  |  |           if (appId !== name) { | 
					
						
							|  |  |  |             localCache.statics[appId] = localCache.statics[name]; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (!localCache.statics[appId]) { | 
					
						
							|  |  |  |         localCache.statics[appId] = { handler: notConfigured, createdAt: Date.now() }; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       localCache.staticsKeys = Object.keys(localCache.statics).sort(shortToLong); | 
					
						
							|  |  |  |       return localCache.statics[appId]; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function serveStatic(req, res, next) { | 
					
						
							|  |  |  |     // If we get this far we can be pretty confident that
 | 
					
						
							|  |  |  |     // the domain was already set up because it's encrypted
 | 
					
						
							|  |  |  |     var appId = req.hostname + req.url.replace(/\/+/g, '#').replace(/#$/, ''); | 
					
						
							|  |  |  |     var appIdParts = appId.split('#'); | 
					
						
							|  |  |  |     var appIdPart; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // TODO configuration for allowing www
 | 
					
						
							|  |  |  |     if (/^www\./.test(req.hostname)) { | 
					
						
							|  |  |  |       // NOTE: acme responder and appcache unbricker must come before scrubTheDub
 | 
					
						
							|  |  |  |       if (/\.(appcache|manifest)\b/.test(req.url)) { | 
					
						
							|  |  |  |         require('./unbrick-appcache').unbrick(req, res); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       require('./no-www').scrubTheDub(req, res); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     /* | 
					
						
							|  |  |  |     if (!redirectives && config.redirects) { | 
					
						
							|  |  |  |       redirectives = require('./hostname-redirects').compile(config.redirects); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // TODO assets.example.com/sub/assets/com.example.xyz/
 | 
					
						
							|  |  |  |     if (/^api\./.test(req.hostname) && /\/api(\/|$)/.test(req.url)) { | 
					
						
							|  |  |  |       // supports api.example.com/sub/app/api/com.example.xyz/
 | 
					
						
							|  |  |  |       if (!apiApp) { | 
					
						
							|  |  |  |         apiApp = require('./apis').create(xconfx, apiFactories, apiDeps); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2016-06-08 14:05:10 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |       if (/^OPTIONS$/i.test(req.method)) { | 
					
						
							|  |  |  |         if (!cors) { | 
					
						
							|  |  |  |           CORS = require('connect-cors'); | 
					
						
							|  |  |  |           cors = CORS({ credentials: true, headers: [ | 
					
						
							|  |  |  |             'X-Requested-With' | 
					
						
							|  |  |  |           , 'X-HTTP-Method-Override' | 
					
						
							|  |  |  |           , 'Content-Type' | 
					
						
							|  |  |  |           , 'Accept' | 
					
						
							|  |  |  |           , 'Authorization' | 
					
						
							|  |  |  |           ], methods: [ "GET", "POST", "PATCH", "PUT", "DELETE" ] }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         cors(req, res, apiApp); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-09 19:14:00 -04:00
										 |  |  |       apiApp(req, res, next); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     while (appIdParts.length) { | 
					
						
							|  |  |  |       // TODO needs IPC to expire cache
 | 
					
						
							|  |  |  |       appIdPart = appIdParts.join('#'); | 
					
						
							|  |  |  |       if (localCache.statics[appIdPart]) { | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       // TODO test via staticsKeys
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       appIdParts.pop(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!appIdPart || !localCache.statics[appIdPart]) { | 
					
						
							|  |  |  |       return staticHelper(appId).then(function () { | 
					
						
							|  |  |  |         localCache.statics[appId].handler(req, res, next); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     localCache.statics[appIdPart].handler(req, res, next); | 
					
						
							|  |  |  |     if (Date.now() - localCache.statics[appIdPart].createdAt > (5 * 60 * 1000)) { | 
					
						
							|  |  |  |       staticHelper(appId, { clear: true }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   app.use('/', serveStatic); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return PromiseA.resolve(); | 
					
						
							|  |  |  | }; |