UNPKG

16 kBJavaScriptView Raw
1var path = require('path')
2var fs = require('fs')
3var helpers = require('./helpers')
4var mime = require('mime-types')
5var terraform = require('terraform')
6var pkg = require('../package.json')
7var skin = require('./skin')
8var connect = require('connect')
9var send = require('send')
10var utilsPause = require('pause')
11var utilsEscape = require('escape-html')
12var parse = require('parseurl')
13var url = require('url')
14var auth = require('basic-auth')
15
16exports.notMultihostURL = function(req, rsp, next){
17 var host = req.headers.host
18 var hostname = host.split(':')[0]
19 var arr = hostname.split(".")
20 var port = host.split(':')[1] ? ':' + host.split(':')[1] : ''
21
22 if(hostname == "127.0.0.1" || hostname == "localhost"){
23 rsp.statusCode = 307
24 rsp.setHeader('Location', 'http://lvh.me' + port)
25 rsp.end("redirecting you to http://lvh.me" + port)
26 }else if(arr.length == 4){
27 arr.pop()
28 arr.push('io')
29 var link = 'http://' + arr.join('.') + port
30 var body = "Local server does not support history. Perhaps you are looking for <href='" + link + "'>" + link + "</a>."
31 rsp.statusCode = 307
32 rsp.end(body)
33 }else if(arr.length > 4){
34 arr.shift()
35 var link = 'http://' + arr.join('.') + port
36 rsp.statusCode = 307
37 rsp.setHeader('Location', link)
38 rsp.end("redirecting you to " + link)
39 }else{
40 next()
41 }
42}
43
44var reservedDomains = ["harp.io", "harpdev.io", "harpapp.io"];
45exports.index = function(dirPath){
46 return function(req, rsp, next){
47 var host = req.headers.host;
48 var hostname = host.split(':')[0];
49 var arr = hostname.split(".");
50 var port = host.split(':')[1] ? ':' + host.split(':')[1] : '';
51 var poly = terraform.root(__dirname + "/templates");
52
53 if(arr.length == 2){
54 fs.readdir(dirPath, function(err, files){
55 var projects = [];
56
57 files.forEach(function(file){
58 var local = file.split('.');
59
60 var appPart = local.join("_");
61
62 if (local.length > 2) {
63 var domain = local.slice(Math.max(local.length - 2, 1)).join(".");
64 if (reservedDomains.indexOf(domain) != -1) {
65 appPart = local[0];
66 }
67 }
68
69 // DOT files are ignored.
70 if (file[0] !== ".") {
71 projects.push({
72 "name" : file,
73 "localUrl" : 'http://' + appPart + "." + host,
74 "localPath" : path.resolve(dirPath, file)
75 });
76 }
77 });
78
79 poly.render("index.jade", { pkg: pkg, projects: projects, layout: "_layout.jade" }, function(error, body){
80 rsp.end(body)
81 });
82 })
83 } else {
84 next();
85 }
86 }
87}
88
89exports.hostProjectFinder = function(dirPath){
90 return function(req, rsp, next){
91 var host = req.headers.host;
92 var hostname = host.split(':')[0];
93 var matches = [];
94
95 fs.readdir(dirPath, function(err, files){
96 var appPart = hostname.split(".")[0];
97 files.forEach(function(file){
98 var fp = file.split('.');
99 var filePart;
100 // Check against Reserved Domains first.
101 if (fp.length > 2) {
102 var domain = fp.slice(Math.max(fp.length - 2, 1)).join(".");
103 if (reservedDomains.indexOf(domain) != -1) {
104 fp = fp.slice(0, Math.max(fp.length - 2))
105 }
106 }
107
108 filePart = fp.join("_");
109 if (appPart == filePart) {
110 matches.push(file);
111 }
112 });
113
114 if(matches.length > 0){
115 req.projectPath = path.resolve(dirPath, matches[0]);
116 next();
117 } else {
118 rsp.end("Cannot find project")
119 }
120
121 });
122 }
123}
124
125exports.regProjectFinder = function(projectPath){
126 return function(req, rsp, next){
127 req.projectPath = projectPath
128 next()
129 }
130}
131
132/**
133 * Fallbacks
134 *
135 * This is the logic behind rendering fallback files.
136 *
137 * 1. return static 200.html file
138 * 2. compile and return 200.xxx
139 * 3. return static 404.html file
140 * 4. compile and return 404.xxx file
141 * 5. default 404
142 *
143 * It is broken into two public functions `fallback`, and `notFound`
144 *
145 */
146
147var fallback = exports.fallback = function(req, rsp, next){
148 skin(req, rsp, [custom200static, custom200dynamic, notFound], next)
149}
150
151var notFound = exports.notFound = function(req, rsp, next){
152 skin(req, rsp, [custom404static, custom404dynamic, default404], next)
153}
154
155
156/**
157 * Custom 200
158 *
159 * 1. return static 200.html file
160 * 2. compile and return 200.xxx file
161 *
162 */
163
164var custom200static = function(req, rsp, next){
165 fs.readFile(path.resolve(req.setup.publicPath, "200.html"), function(err, contents){
166 if(contents){
167 var body = contents.toString()
168 var type = helpers.mimeType("html")
169 var charset = mime.charsets.lookup(type)
170 rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''))
171 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
172 rsp.statusCode = 200
173 rsp.end(body)
174 }else{
175 next()
176 }
177 })
178}
179
180/**
181 * Custom 200 (jade, md, ejs)
182 *
183 * 1. return static 200.html file
184 * 2. compile and return 404.xxx file
185 *
186 */
187
188var custom200dynamic = function(req, rsp, next){
189 skin(req, rsp, [poly], function(){
190 var priorityList = terraform.helpers.buildPriorityList("200.html")
191 var sourceFile = terraform.helpers.findFirstFile(req.setup.publicPath, priorityList)
192 if(!sourceFile) return next()
193
194 req.poly.render(sourceFile, function(error, body){
195 if(error){
196 // TODO: make this better
197 rsp.statusCode = 404;
198 rsp.end("There is an error in your " + sourceFile + " file")
199 }else{
200 if(!body) return next()
201 var type = helpers.mimeType("html")
202 var charset = mime.charsets.lookup(type)
203 rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
204 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
205 rsp.statusCode = 200;
206 rsp.end(body)
207 }
208 })
209 })
210}
211
212
213/**
214 * Custom 404 (html)
215 *
216 * 1. return static 404.html file
217 * 2. compile and return 404.xxx file
218 *
219 * TODO: cache readFile IO
220 *
221 */
222
223var custom404static = function(req, rsp, next){
224 fs.readFile(path.resolve(req.setup.publicPath, "404.html"), function(err, contents){
225 if(contents){
226 var body = contents.toString()
227 var type = helpers.mimeType("html")
228 var charset = mime.charsets.lookup(type)
229 rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''))
230 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
231 rsp.statusCode = 404
232 rsp.end(body)
233 }else{
234 next()
235 }
236 })
237}
238
239
240/**
241 * Custom 404 (jade, md, ejs)
242 *
243 * 1. return static 404.html file
244 * 2. compile and return 404.xxx file
245 *
246 */
247
248var custom404dynamic = function(req, rsp, next){
249 skin(req, rsp, [poly], function(){
250 var priorityList = terraform.helpers.buildPriorityList("404.html")
251 var sourceFile = terraform.helpers.findFirstFile(req.setup.publicPath, priorityList)
252 if(!sourceFile) return next()
253
254 req.poly.render(sourceFile, function(error, body){
255 if(error){
256 // TODO: make this better
257 rsp.statusCode = 404;
258 rsp.end("There is an error in your " + sourceFile + " file")
259 }else{
260 if(!body) return next()
261 var type = helpers.mimeType("html")
262 var charset = mime.charsets.lookup(type)
263 rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
264 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
265 rsp.statusCode = 404;
266 rsp.end(body)
267 }
268 })
269 })
270}
271
272
273/**
274 * Default 404
275 *
276 * No 200 nor 404 files were found.
277 *
278 */
279
280var default404 = function(req, rsp, next){
281 var locals = {
282 project: req.headers.host,
283 name: "Page Not Found",
284 pkg: pkg
285 }
286 terraform.root(__dirname + "/templates").render("404.jade", locals, function(err, body){
287 var type = helpers.mimeType("html")
288 var charset = mime.charsets.lookup(type)
289 rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
290 rsp.statusCode = 404
291 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
292 rsp.end(body)
293 })
294}
295
296
297/**
298 * Underscore
299 *
300 * Returns 404 if path contains beginning underscore
301 *
302 */
303exports.underscore = function(req, rsp, next){
304 if(terraform.helpers.shouldIgnore(req.url)){
305 notFound(req, rsp, next)
306 }else{
307 next()
308 }
309}
310
311/**
312 * Modern Web Language
313 *
314 * Returns 404 if file is a precompiled
315 *
316 */
317exports.mwl = function(req, rsp, next){
318 var ext = path.extname(req.url).replace(/^\./, '')
319 req.originalExt = ext
320
321 // This prevents the source files from being served, but also
322 // has to factor in that in this brave new world, sometimes
323 // `.html` (Handlebars, others), `.css` (PostCSS), and
324 // `.js` (Browserify) are actually being used to specify
325 // source files
326
327 //if (['js'].indexOf(ext) === -1) {
328 if (terraform.helpers.processors["html"].indexOf(ext) !== -1 || terraform.helpers.processors["css"].indexOf(ext) !== -1 || terraform.helpers.processors["js"].indexOf(ext) !== -1) {
329 notFound(req, rsp, next)
330 } else {
331 next()
332 }
333 //} else {
334 //next()
335 //}
336}
337
338/**
339 * Static
340 *
341 * Serves up static page (if it exists).
342 *
343 */
344exports.static = function(req, res, next) {
345 var options = {}
346 var redirect = true
347
348 if ('GET' != req.method && 'HEAD' != req.method) return next()
349 //if (['js'].indexOf(path.extname(req.url).replace(/^\./, '')) !== -1) return next()
350
351 var pathn = parse(req).pathname;
352 var pause = utilsPause(req);
353
354 function resume() {
355 next();
356 pause.resume();
357 }
358
359 function directory() {
360
361 if (!redirect) return resume();
362 var pathname = url.parse(req.originalUrl).pathname;
363 res.statusCode = 301;
364 res.setHeader('Location', pathname + '/');
365 res.end('Redirecting to ' + utilsEscape(pathname) + '/');
366 }
367
368 function error(err) {
369 if (404 == err.status){
370 // look for implicit `*.html` if we get a 404
371 return path.extname(err.path) === ''
372 ? serve(pathn + ".html")
373 : resume()
374 }
375 next(err);
376 }
377
378 var serve = function(pathn){
379 send(req, pathn, {
380 maxage: options.maxAge || 0,
381 root: req.setup.publicPath,
382 hidden: options.hidden
383 })
384 .on('error', error)
385 .on('directory', directory)
386 .pipe(res)
387 }
388 serve(pathn)
389}
390
391/**
392 * Opens the (optional) harp.json file and sets the config settings.
393 */
394
395exports.setup = function(req, rsp, next){
396 if(req.hasOwnProperty('setup')) return next()
397
398 try{
399 req.setup = helpers.setup(req.projectPath)
400 }catch(error){
401 error.stack = helpers.stacktrace(error.stack, { lineno: error.lineno })
402
403 var locals = {
404 project: req.headers.host,
405 error: error,
406 pkg: pkg
407 }
408
409 return terraform.root(__dirname + "/templates").render("error.jade", locals, function(err, body){
410 rsp.statusCode = 500
411 rsp.end(body)
412 })
413 }
414
415 next()
416}
417
418/**
419 * Basic Auth
420 */
421
422exports.basicAuth = function(req, res, next){
423 // default empty
424 var creds = []
425
426 // allow array
427 if(req.setup.config.hasOwnProperty("basicAuth") && req.setup.config["basicAuth"] instanceof Array)
428 creds = req.setup.config["basicAuth"]
429
430 // allow string
431 if(req.setup.config.hasOwnProperty("basicAuth") && typeof req.setup.config["basicAuth"] === 'string')
432 creds = [req.setup.config["basicAuth"]]
433
434 // move on if no creds
435 if(creds.length === 0) return next()
436
437 function check (name, pass) {
438 return creds.some(function(cred){
439 return cred === name + ":" + pass
440 })
441 }
442 var credentials = auth(req)
443 if (!credentials || !check(credentials.name, credentials.pass)) {
444 res.statusCode = 401
445 res.setHeader('WWW-Authenticate', 'Basic realm="example"')
446 res.end('Access denied')
447 } else {
448 res.end('Access granted')
449 }
450
451
452}
453
454/**
455 * Sets up the poly object
456 */
457
458var poly = exports.poly = function(req, rsp, next){
459 if(req.hasOwnProperty("poly")) return next()
460
461 try{
462 req.poly = terraform.root(req.setup.publicPath, req.setup.config.globals)
463 }catch(error){
464 error.stack = helpers.stacktrace(error.stack, { lineno: error.lineno })
465 var locals = {
466 project: req.headers.host,
467 error: error,
468 pkg: pkg
469 }
470 return terraform.root(__dirname + "/templates").render("error.jade", locals, function(err, body){
471 rsp.statusCode = 500
472 rsp.end(body)
473 })
474 }
475 next()
476}
477
478
479/**
480 * Asset Pipeline
481 */
482
483exports.process = function(req, rsp, next){
484 var normalizedPath = helpers.normalizeUrl(req.url)
485 var priorityList = terraform.helpers.buildPriorityList(normalizedPath)
486 var sourceFile = terraform.helpers.findFirstFile(req.setup.publicPath, priorityList)
487
488
489 /**
490 * We GTFO if we don't have a source file.
491 */
492
493 if(!sourceFile){
494 if (path.basename(normalizedPath) === "index.html") {
495 var pathAr = normalizedPath.split(path.sep); pathAr.pop() // Pop index.html off the list
496 var prospectCleanPath = pathAr.join("/")
497 var prospectNormalizedPath = helpers.normalizeUrl(prospectCleanPath)
498 var prospectPriorityList = terraform.helpers.buildPriorityList(prospectNormalizedPath)
499 prospectPriorityList.push(path.basename(prospectNormalizedPath + ".html"))
500
501 sourceFile = terraform.helpers.findFirstFile(req.setup.publicPath, prospectPriorityList)
502
503 if (!sourceFile) {
504 return next()
505 } else {
506 // 301 redirect
507 rsp.statusCode = 301
508 rsp.setHeader('Location', prospectCleanPath)
509 rsp.end('Redirecting to ' + utilsEscape(prospectCleanPath))
510 }
511
512 } else {
513 return next()
514 }
515 } else {
516
517 /**
518 * Now we let terraform handle the asset pipeline.
519 */
520
521 req.poly.render(sourceFile, function(error, body){
522 if(error){
523 error.stack = helpers.stacktrace(error.stack, { lineno: error.lineno })
524
525 var locals = {
526 project: req.headers.host,
527 error: error,
528 pkg: pkg
529 }
530 if(terraform.helpers.outputType(sourceFile) == 'css'){
531 var outputType = terraform.helpers.outputType(sourceFile)
532 var mimeType = helpers.mimeType(outputType)
533 var charset = mime.charsets.lookup(mimeType)
534 var body = helpers.cssError(locals)
535 rsp.statusCode = 200
536 rsp.setHeader('Content-Type', mimeType + (charset ? '; charset=' + charset : ''))
537 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
538 rsp.end(body)
539 }else{
540
541 // Make the paths relative but keep the root dir.
542 // TODO: move to helper.
543 //
544 // var loc = req.projectPath.split(path.sep); loc.pop()
545 // var loc = loc.join(path.sep) + path.sep
546 // if(error.filename) error.filename = error.filename.replace(loc, "")
547
548 terraform.root(__dirname + "/templates").render("error.jade", locals, function(err, body){
549 var mimeType = helpers.mimeType('html')
550 var charset = mime.charsets.lookup(mimeType)
551 rsp.statusCode = 500
552 rsp.setHeader('Content-Type', mimeType + (charset ? '; charset=' + charset : ''))
553 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
554 rsp.end(body)
555 })
556 }
557 }else{
558 // 404
559 if(!body) return next()
560
561 var outputType = terraform.helpers.outputType(sourceFile)
562 var mimeType = helpers.mimeType(outputType)
563 var charset = mime.charsets.lookup(mimeType)
564 rsp.statusCode = 200
565 rsp.setHeader('Content-Type', mimeType + (charset ? '; charset=' + charset : ''))
566 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
567 rsp.end(body);
568 }
569 })
570 }
571
572
573
574
575
576
577
578
579
580
581}