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