UNPKG

18.8 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
133exports.buildFallbackPriorityList = function(filePath){
134 var list = []
135 var popAndPush = function(arr){
136 if (arr.length === 1) return list
137 arr.pop()
138 var path200 = arr.join("/") + "/200.html"
139 var path404 = arr.join("/") + "/404.html"
140 var list200 = terraform.helpers.buildPriorityList(path200)
141 var list404 = terraform.helpers.buildPriorityList(path404)
142 list = list.concat(list200).concat(list404)
143 return popAndPush(arr)
144 }
145 return popAndPush(filePath.split("/"))
146}
147
148
149/**
150 * Fallback2
151 *
152 * This is a reimplementation of Fallback (see below) with the goal of supporting nested 200 & 404 pages.
153 *
154 * 1. make a Priority List of wanted files
155 * 2. make list of actual files.
156 * 3. find by priority and compile/serve that file.
157 * 5. default 404
158 *
159 * It is broken into two public functions `fallback`, and `notFound`
160 *
161 */
162
163var fallbackCascade = function(req, rsp, next){
164 var pathn = parse(req).pathname;
165 var priorityList = helpers.buildFallbackPriorityList(pathn)
166
167 helpers.ls(req.setup.publicPath, function(error, fileList){
168 if (error) return next()
169
170 var fallbackFile = priorityList.find(function(wantedFallbackFile){
171 return (fileList.indexOf(wantedFallbackFile) !== -1)
172 })
173
174 if(!fallbackFile) return next()
175
176 var statusCode = fallbackFile.indexOf("200") !== -1 ? 200 : 404
177
178
179
180 req.poly.render(fallbackFile, function(error, body){
181 if(error){
182 rsp.statusCode = 404;
183 rsp.end("There is an error in your " + fallbackFile + " file")
184 }else{
185 if(!body) return next()
186 var type = helpers.mimeType("html")
187 var charset = mime.charsets.lookup(type)
188 rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
189 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
190 rsp.statusCode = statusCode
191 rsp.end(body)
192 }
193 })
194
195 // fs.readFile(path.resolve(req.setup.publicPath, fallbackFile), function(err, contents){
196 // if(contents){
197 // var body = contents.toString()
198 // var type = helpers.mimeType("html")
199 // var charset = mime.charsets.lookup(type)
200 // rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''))
201 // rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
202 // rsp.statusCode = statusCode
203 // rsp.end(body)
204 // }else{
205 // return next()
206 // }
207 // })
208
209 })
210
211}
212
213var fallback = exports.fallback2 = function(req, rsp, next){
214 skin(req, rsp, [fallbackCascade, custom200static, custom404static, default404], next)
215}
216
217// var notFound = exports.notFound = function(req, rsp, next){
218// skin(req, rsp, [custom404static, custom404dynamic, default404], next)
219// }
220
221
222
223/**
224 * Fallback
225 *
226 * This is the logic behind rendering fallback files.
227 *
228 * 1. return static 200.html file
229 * 2. compile and return 200.xxx
230 * 3. return static 404.html file
231 * 4. compile and return 404.xxx file
232 * 5. default 404
233 *
234 * It is broken into two public functions `fallback`, and `notFound`
235 *
236 */
237
238var fallback = exports.fallback = function(req, rsp, next){
239 skin(req, rsp, [custom200static, custom200dynamic, notFound], next)
240}
241
242var notFound = exports.notFound = function(req, rsp, next){
243 skin(req, rsp, [custom404static, custom404dynamic, default404], next)
244}
245
246
247/**
248 * Custom 200
249 *
250 * 1. return static 200.html file
251 * 2. compile and return 200.xxx file
252 *
253 */
254
255var custom200static = function(req, rsp, next){
256 fs.readFile(path.resolve(req.setup.publicPath, "200.html"), function(err, contents){
257 if(contents){
258 var body = contents.toString()
259 var type = helpers.mimeType("html")
260 var charset = mime.charsets.lookup(type)
261 rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''))
262 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
263 rsp.statusCode = 200
264 rsp.end(body)
265 }else{
266 next()
267 }
268 })
269}
270
271/**
272 * Custom 200 (jade, md, ejs)
273 *
274 * 1. return static 200.html file
275 * 2. compile and return 404.xxx file
276 *
277 */
278
279var custom200dynamic = function(req, rsp, next){
280 skin(req, rsp, [poly], function(){
281 var priorityList = terraform.helpers.buildPriorityList("200.html")
282 var sourceFile = terraform.helpers.findFirstFile(req.setup.publicPath, priorityList)
283 if(!sourceFile) return next()
284
285 req.poly.render(sourceFile, function(error, body){
286 if(error){
287 // TODO: make this better
288 rsp.statusCode = 404;
289 rsp.end("There is an error in your " + sourceFile + " file")
290 }else{
291 if(!body) return next()
292 var type = helpers.mimeType("html")
293 var charset = mime.charsets.lookup(type)
294 rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
295 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
296 rsp.statusCode = 200;
297 rsp.end(body)
298 }
299 })
300 })
301}
302
303
304/**
305 * Custom 404 (html)
306 *
307 * 1. return static 404.html file
308 * 2. compile and return 404.xxx file
309 *
310 * TODO: cache readFile IO
311 *
312 */
313
314var custom404static = function(req, rsp, next){
315 fs.readFile(path.resolve(req.setup.publicPath, "404.html"), function(err, contents){
316 if(contents){
317 var body = contents.toString()
318 var type = helpers.mimeType("html")
319 var charset = mime.charsets.lookup(type)
320 rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''))
321 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
322 rsp.statusCode = 404
323 rsp.end(body)
324 }else{
325 next()
326 }
327 })
328}
329
330
331/**
332 * Custom 404 (jade, md, ejs)
333 *
334 * 1. return static 404.html file
335 * 2. compile and return 404.xxx file
336 *
337 */
338
339var custom404dynamic = function(req, rsp, next){
340 skin(req, rsp, [poly], function(){
341 var priorityList = terraform.helpers.buildPriorityList("404.html")
342 var sourceFile = terraform.helpers.findFirstFile(req.setup.publicPath, priorityList)
343 if(!sourceFile) return next()
344
345 req.poly.render(sourceFile, function(error, body){
346 if(error){
347 // TODO: make this better
348 rsp.statusCode = 404;
349 rsp.end("There is an error in your " + sourceFile + " file")
350 }else{
351 if(!body) return next()
352 var type = helpers.mimeType("html")
353 var charset = mime.charsets.lookup(type)
354 rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
355 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
356 rsp.statusCode = 404;
357 rsp.end(body)
358 }
359 })
360 })
361}
362
363
364/**
365 * Default 404
366 *
367 * No 200 nor 404 files were found.
368 *
369 */
370
371var default404 = function(req, rsp, next){
372 var locals = {
373 project: req.headers.host,
374 name: "Page Not Found",
375 pkg: pkg
376 }
377 terraform.root(__dirname + "/templates").render("404.jade", locals, function(err, body){
378 var type = helpers.mimeType("html")
379 var charset = mime.charsets.lookup(type)
380 rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
381 rsp.statusCode = 404
382 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
383 rsp.end(body)
384 })
385}
386
387
388/**
389 * Underscore
390 *
391 * Returns 404 if path contains beginning underscore
392 *
393 */
394exports.underscore = function(req, rsp, next){
395 if(terraform.helpers.shouldIgnore(req.url)){
396 notFound(req, rsp, next)
397 }else{
398 next()
399 }
400}
401
402/**
403 * Modern Web Language
404 *
405 * Returns 404 if file is a precompiled
406 *
407 */
408exports.mwl = function(req, rsp, next){
409 var ext = path.extname(req.url).replace(/^\./, '')
410 req.originalExt = ext
411
412 // This prevents the source files from being served, but also
413 // has to factor in that in this brave new world, sometimes
414 // `.html` (Handlebars, others), `.css` (PostCSS), and
415 // `.js` (Browserify) are actually being used to specify
416 // source files
417
418 //if (['js'].indexOf(ext) === -1) {
419 if (terraform.helpers.processors["html"].indexOf(ext) !== -1 || terraform.helpers.processors["css"].indexOf(ext) !== -1 || terraform.helpers.processors["js"].indexOf(ext) !== -1) {
420 notFound(req, rsp, next)
421 } else {
422 next()
423 }
424 //} else {
425 //next()
426 //}
427}
428
429/**
430 * Static
431 *
432 * Serves up static page (if it exists).
433 *
434 */
435exports.static = function(req, res, next) {
436 var options = {}
437 var redirect = true
438
439 if ('GET' != req.method && 'HEAD' != req.method) return next()
440 //if (['js'].indexOf(path.extname(req.url).replace(/^\./, '')) !== -1) return next()
441
442 var pathn = parse(req).pathname;
443 var pause = utilsPause(req);
444
445 function resume() {
446 next();
447 pause.resume();
448 }
449
450 function directory() {
451
452 if (!redirect) return resume();
453 var pathname = url.parse(req.originalUrl).pathname;
454 res.statusCode = 301;
455 res.setHeader('Location', pathname + '/');
456 res.end('Redirecting to ' + utilsEscape(pathname) + '/');
457 }
458
459 function error(err) {
460 if (404 == err.status){
461 // look for implicit `*.html` if we get a 404
462 return path.extname(err.path) === ''
463 ? serve(pathn + ".html")
464 : resume()
465 }
466 next(err);
467 }
468
469 var serve = function(pathn){
470 send(req, pathn, {
471 maxage: options.maxAge || 0,
472 root: req.setup.publicPath,
473 hidden: options.hidden
474 })
475 .on('error', error)
476 .on('directory', directory)
477 .pipe(res)
478 }
479 serve(pathn)
480}
481
482/**
483 * Opens the (optional) harp.json file and sets the config settings.
484 */
485
486exports.setup = function(req, rsp, next){
487 if(req.hasOwnProperty('setup')) return next()
488
489 try{
490 req.setup = helpers.setup(req.projectPath)
491 }catch(error){
492 error.stack = helpers.stacktrace(error.stack, { lineno: error.lineno })
493
494 var locals = {
495 project: req.headers.host,
496 error: error,
497 pkg: pkg
498 }
499
500 return terraform.root(__dirname + "/templates").render("error.jade", locals, function(err, body){
501 rsp.statusCode = 500
502 rsp.end(body)
503 })
504 }
505
506 next()
507}
508
509/**
510 * Basic Auth
511 */
512
513exports.basicAuth = function(req, res, next){
514 // default empty
515 var creds = []
516
517 // allow array
518 if(req.setup.config.hasOwnProperty("basicAuth") && req.setup.config["basicAuth"] instanceof Array)
519 creds = req.setup.config["basicAuth"]
520
521 // allow string
522 if(req.setup.config.hasOwnProperty("basicAuth") && typeof req.setup.config["basicAuth"] === 'string')
523 creds = [req.setup.config["basicAuth"]]
524
525 // move on if no creds
526 if(creds.length === 0) return next()
527
528 function check (name, pass) {
529 return creds.some(function(cred){
530 return cred === name + ":" + pass
531 })
532 }
533 var credentials = auth(req)
534 if (!credentials || !check(credentials.name, credentials.pass)) {
535 res.statusCode = 401
536 res.setHeader('WWW-Authenticate', 'Basic realm="example"')
537 res.end('Access denied')
538 } else {
539 res.end('Access granted')
540 }
541
542
543}
544
545/**
546 * Sets up the poly object
547 */
548
549var poly = exports.poly = function(req, rsp, next){
550 if(req.hasOwnProperty("poly")) return next()
551
552 try{
553 req.poly = terraform.root(req.setup.publicPath, req.setup.config.globals)
554 }catch(error){
555 error.stack = helpers.stacktrace(error.stack, { lineno: error.lineno })
556 var locals = {
557 project: req.headers.host,
558 error: error,
559 pkg: pkg
560 }
561 return terraform.root(__dirname + "/templates").render("error.jade", locals, function(err, body){
562 rsp.statusCode = 500
563 rsp.end(body)
564 })
565 }
566 next()
567}
568
569
570/**
571 * Asset Pipeline
572 */
573
574exports.process = function(req, rsp, next){
575 var normalizedPath = helpers.normalizeUrl(req.url)
576 var priorityList = terraform.helpers.buildPriorityList(normalizedPath)
577 var sourceFile = terraform.helpers.findFirstFile(req.setup.publicPath, priorityList)
578
579
580 /**
581 * We GTFO if we don't have a source file.
582 */
583
584 if(!sourceFile){
585 if (path.basename(normalizedPath) === "index.html") {
586 var pathAr = normalizedPath.split(path.sep); pathAr.pop() // Pop index.html off the list
587 var prospectCleanPath = pathAr.join("/")
588 var prospectNormalizedPath = helpers.normalizeUrl(prospectCleanPath)
589 var prospectPriorityList = terraform.helpers.buildPriorityList(prospectNormalizedPath)
590 prospectPriorityList.push(path.basename(prospectNormalizedPath + ".html"))
591
592 sourceFile = terraform.helpers.findFirstFile(req.setup.publicPath, prospectPriorityList)
593
594 if (!sourceFile) {
595 return next()
596 } else {
597 // 301 redirect
598 rsp.statusCode = 301
599 rsp.setHeader('Location', prospectCleanPath)
600 rsp.end('Redirecting to ' + utilsEscape(prospectCleanPath))
601 }
602
603 } else {
604 return next()
605 }
606 } else {
607
608 /**
609 * Now we let terraform handle the asset pipeline.
610 */
611
612 req.poly.render(sourceFile, function(error, body){
613 if(error){
614 error.stack = helpers.stacktrace(error.stack, { lineno: error.lineno })
615
616 var locals = {
617 project: req.headers.host,
618 error: error,
619 pkg: pkg
620 }
621 if(terraform.helpers.outputType(sourceFile) == 'css'){
622 var outputType = terraform.helpers.outputType(sourceFile)
623 var mimeType = helpers.mimeType(outputType)
624 var charset = mime.charsets.lookup(mimeType)
625 var body = helpers.cssError(locals)
626 rsp.statusCode = 200
627 rsp.setHeader('Content-Type', mimeType + (charset ? '; charset=' + charset : ''))
628 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
629 rsp.end(body)
630 }else{
631
632 // Make the paths relative but keep the root dir.
633 // TODO: move to helper.
634 //
635 // var loc = req.projectPath.split(path.sep); loc.pop()
636 // var loc = loc.join(path.sep) + path.sep
637 // if(error.filename) error.filename = error.filename.replace(loc, "")
638
639 terraform.root(__dirname + "/templates").render("error.jade", locals, function(err, body){
640 var mimeType = helpers.mimeType('html')
641 var charset = mime.charsets.lookup(mimeType)
642 rsp.statusCode = 500
643 rsp.setHeader('Content-Type', mimeType + (charset ? '; charset=' + charset : ''))
644 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
645 rsp.end(body)
646 })
647 }
648 }else{
649 // 404
650 if(!body) return next()
651
652 var outputType = terraform.helpers.outputType(sourceFile)
653 var mimeType = helpers.mimeType(outputType)
654 var charset = mime.charsets.lookup(mimeType)
655 rsp.statusCode = 200
656 rsp.setHeader('Content-Type', mimeType + (charset ? '; charset=' + charset : ''))
657 rsp.setHeader('Content-Length', Buffer.byteLength(body, charset));
658 rsp.end(body);
659 }
660 })
661 }
662
663
664
665
666
667
668
669
670
671
672}