PageRenderTime 27ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/node_modules/serve-index/index.js

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