Initial commit for OpenECOMP SDN-C OA&M
[sdnc/oam.git] / dgbuilder / dgeflows / node_modules / serve-index / index.js
1 /*!
2  * serve-index
3  * Copyright(c) 2011 Sencha Inc.
4  * Copyright(c) 2011 TJ Holowaychuk
5  * Copyright(c) 2014 Douglas Christopher Wilson
6  * MIT Licensed
7  */
8
9 // TODO: arrow key navigation
10 // TODO: make icons extensible
11
12 /**
13  * Module dependencies.
14  */
15
16 var accepts = require('accepts');
17 var createError = require('http-errors');
18 var debug = require('debug')('serve-index');
19 var fs = require('fs')
20   , path = require('path')
21   , normalize = path.normalize
22   , sep = path.sep
23   , extname = path.extname
24   , join = path.join;
25 var Batch = require('batch');
26 var mime = require('mime-types');
27 var parseUrl = require('parseurl');
28 var resolve = require('path').resolve;
29
30 /*!
31  * Icon cache.
32  */
33
34 var cache = {};
35
36 /*!
37  * Default template.
38  */
39
40 var defaultTemplate = join(__dirname, 'public', 'directory.html');
41
42 /*!
43  * Stylesheet.
44  */
45
46 var defaultStylesheet = join(__dirname, 'public', 'style.css');
47
48 /**
49  * Media types and the map for content negotiation.
50  */
51
52 var mediaTypes = [
53   'text/html',
54   'text/plain',
55   'application/json'
56 ];
57
58 var mediaType = {
59   'text/html': 'html',
60   'text/plain': 'plain',
61   'application/json': 'json'
62 };
63
64 /**
65  * Serve directory listings with the given `root` path.
66  *
67  * See Readme.md for documentation of options.
68  *
69  * @param {String} path
70  * @param {Object} options
71  * @return {Function} middleware
72  * @api public
73  */
74
75 exports = module.exports = function serveIndex(root, options){
76   options = options || {};
77
78   // root required
79   if (!root) throw new TypeError('serveIndex() root path required');
80
81   // resolve root to absolute and normalize
82   root = resolve(root);
83   root = normalize(root + sep);
84
85   var hidden = options.hidden
86     , icons = options.icons
87     , view = options.view || 'tiles'
88     , filter = options.filter
89     , template = options.template || defaultTemplate
90     , stylesheet = options.stylesheet || defaultStylesheet;
91
92   return function serveIndex(req, res, next) {
93     if (req.method !== 'GET' && req.method !== 'HEAD') {
94       res.statusCode = 'OPTIONS' === req.method
95         ? 200
96         : 405;
97       res.setHeader('Allow', 'GET, HEAD, OPTIONS');
98       res.end();
99       return;
100     }
101
102     // parse URLs
103     var url = parseUrl(req);
104     var originalUrl = parseUrl.original(req);
105     var dir = decodeURIComponent(url.pathname);
106     var originalDir = decodeURIComponent(originalUrl.pathname);
107
108     // join / normalize from root dir
109     var path = normalize(join(root, dir));
110
111     // null byte(s), bad request
112     if (~path.indexOf('\0')) return next(createError(400));
113
114     // malicious path
115     if ((path + sep).substr(0, root.length) !== root) {
116       debug('malicious path "%s"', path);
117       return next(createError(403));
118     }
119
120     // determine ".." display
121     var showUp = normalize(resolve(path) + sep) !== root;
122
123     // check if we have a directory
124     debug('stat "%s"', path);
125     fs.stat(path, function(err, stat){
126       if (err && err.code === 'ENOENT') {
127         return next();
128       }
129
130       if (err) {
131         err.status = err.code === 'ENAMETOOLONG'
132           ? 414
133           : 500;
134         return next(err);
135       }
136
137       if (!stat.isDirectory()) return next();
138
139       // fetch files
140       debug('readdir "%s"', path);
141       fs.readdir(path, function(err, files){
142         if (err) return next(err);
143         if (!hidden) files = removeHidden(files);
144         if (filter) files = files.filter(function(filename, index, list) {
145           return filter(filename, index, list, path);
146         });
147         files.sort();
148
149         // content-negotiation
150         var accept = accepts(req);
151         var type = accept.type(mediaTypes);
152
153         // not acceptable
154         if (!type) return next(createError(406));
155         exports[mediaType[type]](req, res, files, next, originalDir, showUp, icons, path, view, template, stylesheet);
156       });
157     });
158   };
159 };
160
161 /**
162  * Respond with text/html.
163  */
164
165 exports.html = function(req, res, files, next, dir, showUp, icons, path, view, template, stylesheet){
166   fs.readFile(template, 'utf8', function(err, str){
167     if (err) return next(err);
168     fs.readFile(stylesheet, 'utf8', function(err, style){
169       if (err) return next(err);
170       stat(path, files, function(err, stats){
171         if (err) return next(err);
172         files = files.map(function(file, i){ return { name: file, stat: stats[i] }; });
173         files.sort(fileSort);
174         if (showUp) files.unshift({ name: '..' });
175         str = str
176           .replace(/\{style\}/g, style.concat(iconStyle(files, icons)))
177           .replace(/\{files\}/g, html(files, dir, icons, view))
178           .replace(/\{directory\}/g, dir)
179           .replace(/\{linked-path\}/g, htmlPath(dir));
180
181         var buf = new Buffer(str, 'utf8');
182         res.setHeader('Content-Type', 'text/html; charset=utf-8');
183         res.setHeader('Content-Length', buf.length);
184         res.end(buf);
185       });
186     });
187   });
188 };
189
190 /**
191  * Respond with application/json.
192  */
193
194 exports.json = function(req, res, files){
195   var body = JSON.stringify(files);
196   var buf = new Buffer(body, 'utf8');
197
198   res.setHeader('Content-Type', 'application/json; charset=utf-8');
199   res.setHeader('Content-Length', buf.length);
200   res.end(buf);
201 };
202
203 /**
204  * Respond with text/plain.
205  */
206
207 exports.plain = function(req, res, files){
208   var body = files.join('\n') + '\n';
209   var buf = new Buffer(body, 'utf8');
210
211   res.setHeader('Content-Type', 'text/plain; charset=utf-8');
212   res.setHeader('Content-Length', buf.length);
213   res.end(buf);
214 };
215
216 /**
217  * Sort function for with directories first.
218  */
219
220 function fileSort(a, b) {
221   return Number(b.stat && b.stat.isDirectory()) - Number(a.stat && a.stat.isDirectory()) ||
222     String(a.name).toLocaleLowerCase().localeCompare(String(b.name).toLocaleLowerCase());
223 }
224
225 /**
226  * Map html `dir`, returning a linked path.
227  */
228
229 function htmlPath(dir) {
230   var curr = [];
231   return dir.split('/').map(function(part){
232     curr.push(encodeURIComponent(part));
233     return part ? '<a href="' + curr.join('/') + '">' + part + '</a>' : '';
234   }).join(' / ');
235 }
236
237 /**
238  * Get the icon data for the file name.
239  */
240
241 function iconLookup(filename) {
242   var ext = extname(filename);
243
244   // try by extension
245   if (icons[ext]) {
246     return {
247       className: 'icon-' + ext.substring(1),
248       fileName: icons[ext]
249     };
250   }
251
252   var mimetype = mime.lookup(ext);
253
254   // default if no mime type
255   if (mimetype === false) {
256     return {
257       className: 'icon-default',
258       fileName: icons.default
259     };
260   }
261
262   // try by mime type
263   if (icons[mimetype]) {
264     return {
265       className: 'icon-' + mimetype.replace('/', '-'),
266       fileName: icons[mimetype]
267     };
268   }
269
270   var suffix = mimetype.split('+')[1];
271
272   if (suffix && icons['+' + suffix]) {
273     return {
274       className: 'icon-' + suffix,
275       fileName: icons['+' + suffix]
276     };
277   }
278
279   var type = mimetype.split('/')[0];
280
281   // try by type only
282   if (icons[type]) {
283     return {
284       className: 'icon-' + type,
285       fileName: icons[type]
286     };
287   }
288
289   return {
290     className: 'icon-default',
291     fileName: icons.default
292   };
293 }
294
295 /**
296  * Load icon images, return css string.
297  */
298
299 function iconStyle (files, useIcons) {
300   if (!useIcons) return '';
301   var className;
302   var i;
303   var iconName;
304   var list = [];
305   var rules = {};
306   var selector;
307   var selectors = {};
308   var style = '';
309
310   for (i = 0; i < files.length; i++) {
311     var file = files[i];
312
313     var isDir = '..' == file.name || (file.stat && file.stat.isDirectory());
314     var icon = isDir
315       ? { className: 'icon-directory', fileName: icons.folder }
316       : iconLookup(file.name);
317     var iconName = icon.fileName;
318
319     selector = '#files .' + icon.className + ' .name';
320
321     if (!rules[iconName]) {
322       rules[iconName] = 'background-image: url(data:image/png;base64,' + load(iconName) + ');'
323       selectors[iconName] = [];
324       list.push(iconName);
325     }
326
327     if (selectors[iconName].indexOf(selector) === -1) {
328       selectors[iconName].push(selector);
329     }
330   }
331
332   for (i = 0; i < list.length; i++) {
333     iconName = list[i];
334     style += selectors[iconName].join(',\n') + ' {\n  ' + rules[iconName] + '\n}\n';
335   }
336
337   return style;
338 }
339
340 /**
341  * Map html `files`, returning an html unordered list.
342  */
343
344 function html(files, dir, useIcons, view) {
345   return '<ul id="files" class="view-' + view + '">'
346     + (view == 'details' ? (
347       '<li class="header">'
348       + '<span class="name">Name</span>'
349       + '<span class="size">Size</span>'
350       + '<span class="date">Modified</span>'
351       + '</li>') : '')
352     + files.map(function(file){
353     var isDir = '..' == file.name || (file.stat && file.stat.isDirectory())
354       , classes = []
355       , path = dir.split('/').map(function (c) { return encodeURIComponent(c); });
356
357     if (useIcons) {
358       classes.push('icon');
359
360       if (isDir) {
361         classes.push('icon-directory');
362       } else {
363         var ext = extname(file.name);
364         var icon = iconLookup(file.name);
365
366         classes.push('icon');
367         classes.push('icon-' + ext.substring(1));
368
369         if (classes.indexOf(icon.className) === -1) {
370           classes.push(icon.className);
371         }
372       }
373     }
374
375     path.push(encodeURIComponent(file.name));
376
377     var date = file.stat && file.name !== '..'
378       ? file.stat.mtime.toDateString() + ' ' + file.stat.mtime.toLocaleTimeString()
379       : '';
380     var size = file.stat && !isDir
381       ? file.stat.size
382       : '';
383
384     return '<li><a href="'
385       + normalizeSlashes(normalize(path.join('/')))
386       + '" class="'
387       + classes.join(' ') + '"'
388       + ' title="' + file.name + '">'
389       + '<span class="name">'+file.name+'</span>'
390       + '<span class="size">'+size+'</span>'
391       + '<span class="date">'+date+'</span>'
392       + '</a></li>';
393
394   }).join('\n') + '</ul>';
395 }
396
397 /**
398  * Load and cache the given `icon`.
399  *
400  * @param {String} icon
401  * @return {String}
402  * @api private
403  */
404
405 function load(icon) {
406   if (cache[icon]) return cache[icon];
407   return cache[icon] = fs.readFileSync(__dirname + '/public/icons/' + icon, 'base64');
408 }
409
410 /**
411  * Normalizes the path separator from system separator
412  * to URL separator, aka `/`.
413  *
414  * @param {String} path
415  * @return {String}
416  * @api private
417  */
418
419 function normalizeSlashes(path) {
420   return path.split(sep).join('/');
421 };
422
423 /**
424  * Filter "hidden" `files`, aka files
425  * beginning with a `.`.
426  *
427  * @param {Array} files
428  * @return {Array}
429  * @api private
430  */
431
432 function removeHidden(files) {
433   return files.filter(function(file){
434     return '.' != file[0];
435   });
436 }
437
438 /**
439  * Stat all files and return array of stat
440  * in same order.
441  */
442
443 function stat(dir, files, cb) {
444   var batch = new Batch();
445
446   batch.concurrency(10);
447
448   files.forEach(function(file){
449     batch.push(function(done){
450       fs.stat(join(dir, file), function(err, stat){
451         if (err && err.code !== 'ENOENT') return done(err);
452
453         // pass ENOENT as null stat, not error
454         done(null, stat || null);
455       });
456     });
457   });
458
459   batch.end(cb);
460 }
461
462 /**
463  * Icon map.
464  */
465
466 var icons = {
467   // base icons
468   'default': 'page_white.png',
469   'folder': 'folder.png',
470
471   // generic mime type icons
472   'image': 'image.png',
473   'text': 'page_white_text.png',
474   'video': 'film.png',
475
476   // generic mime suffix icons
477   '+json': 'page_white_code.png',
478   '+xml': 'page_white_code.png',
479   '+zip': 'box.png',
480
481   // specific mime type icons
482   'application/font-woff': 'font.png',
483   'application/javascript': 'page_white_code_red.png',
484   'application/json': 'page_white_code.png',
485   'application/msword': 'page_white_word.png',
486   'application/pdf': 'page_white_acrobat.png',
487   'application/postscript': 'page_white_vector.png',
488   'application/rtf': 'page_white_word.png',
489   'application/vnd.ms-excel': 'page_white_excel.png',
490   'application/vnd.ms-powerpoint': 'page_white_powerpoint.png',
491   'application/vnd.oasis.opendocument.presentation': 'page_white_powerpoint.png',
492   'application/vnd.oasis.opendocument.spreadsheet': 'page_white_excel.png',
493   'application/vnd.oasis.opendocument.text': 'page_white_word.png',
494   'application/x-7z-compressed': 'box.png',
495   'application/x-sh': 'application_xp_terminal.png',
496   'application/x-font-ttf': 'font.png',
497   'application/x-msaccess': 'page_white_database.png',
498   'application/x-shockwave-flash': 'page_white_flash.png',
499   'application/x-sql': 'page_white_database.png',
500   'application/x-tar': 'box.png',
501   'application/x-xz': 'box.png',
502   'application/xml': 'page_white_code.png',
503   'application/zip': 'box.png',
504   'image/svg+xml': 'page_white_vector.png',
505   'text/css': 'page_white_code.png',
506   'text/html': 'page_white_code.png',
507   'text/less': 'page_white_code.png',
508
509   // other, extension-specific icons
510   '.accdb': 'page_white_database.png',
511   '.apk': 'box.png',
512   '.app': 'application_xp.png',
513   '.as': 'page_white_actionscript.png',
514   '.asp': 'page_white_code.png',
515   '.aspx': 'page_white_code.png',
516   '.bat': 'application_xp_terminal.png',
517   '.bz2': 'box.png',
518   '.c': 'page_white_c.png',
519   '.cab': 'box.png',
520   '.cfm': 'page_white_coldfusion.png',
521   '.clj': 'page_white_code.png',
522   '.cc': 'page_white_cplusplus.png',
523   '.cgi': 'application_xp_terminal.png',
524   '.cpp': 'page_white_cplusplus.png',
525   '.cs': 'page_white_csharp.png',
526   '.db': 'page_white_database.png',
527   '.dbf': 'page_white_database.png',
528   '.deb': 'box.png',
529   '.dll': 'page_white_gear.png',
530   '.dmg': 'drive.png',
531   '.docx': 'page_white_word.png',
532   '.erb': 'page_white_ruby.png',
533   '.exe': 'application_xp.png',
534   '.fnt': 'font.png',
535   '.gam': 'controller.png',
536   '.gz': 'box.png',
537   '.h': 'page_white_h.png',
538   '.ini': 'page_white_gear.png',
539   '.iso': 'cd.png',
540   '.jar': 'box.png',
541   '.java': 'page_white_cup.png',
542   '.jsp': 'page_white_cup.png',
543   '.lua': 'page_white_code.png',
544   '.lz': 'box.png',
545   '.lzma': 'box.png',
546   '.m': 'page_white_code.png',
547   '.map': 'map.png',
548   '.msi': 'box.png',
549   '.mv4': 'film.png',
550   '.otf': 'font.png',
551   '.pdb': 'page_white_database.png',
552   '.php': 'page_white_php.png',
553   '.pl': 'page_white_code.png',
554   '.pkg': 'box.png',
555   '.pptx': 'page_white_powerpoint.png',
556   '.psd': 'page_white_picture.png',
557   '.py': 'page_white_code.png',
558   '.rar': 'box.png',
559   '.rb': 'page_white_ruby.png',
560   '.rm': 'film.png',
561   '.rom': 'controller.png',
562   '.rpm': 'box.png',
563   '.sass': 'page_white_code.png',
564   '.sav': 'controller.png',
565   '.scss': 'page_white_code.png',
566   '.srt': 'page_white_text.png',
567   '.tbz2': 'box.png',
568   '.tgz': 'box.png',
569   '.tlz': 'box.png',
570   '.vb': 'page_white_code.png',
571   '.vbs': 'page_white_code.png',
572   '.xcf': 'page_white_picture.png',
573   '.xlsx': 'page_white_excel.png',
574   '.yaws': 'page_white_code.png'
575 };