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

/node_modules/serve-index/index.js

https://gitlab.com/laurenmbeatty/react_router
JavaScript | 497 lines | 321 code | 74 blank | 102 comment | 48 complexity | f0112d44c4d50a573eef3d215c99145c MD5 | raw file
  1. /*!
  2. * Expressjs | Connect - directory
  3. * Copyright(c) 2011 Sencha Inc.
  4. * Copyright(c) 2011 TJ Holowaychuk
  5. * Copyright(c) 2014 Douglas Christopher Wilson
  6. * MIT Licensed
  7. */
  8. // TODO: arrow key navigation
  9. // TODO: make icons extensible
  10. /**
  11. * Module dependencies.
  12. */
  13. var http = require('http')
  14. , fs = require('fs')
  15. , parse = require('url').parse
  16. , path = require('path')
  17. , normalize = path.normalize
  18. , sep = path.sep
  19. , extname = path.extname
  20. , join = path.join;
  21. var Batch = require('batch');
  22. var Negotiator = require('negotiator');
  23. /*!
  24. * Icon cache.
  25. */
  26. var cache = {};
  27. /*!
  28. * Default template.
  29. */
  30. var defaultTemplate = join(__dirname, 'public', 'directory.html');
  31. /*!
  32. * Stylesheet.
  33. */
  34. var defaultStylesheet = join(__dirname, 'public', 'style.css');
  35. /**
  36. * Media types and the map for content negotiation.
  37. */
  38. var mediaTypes = [
  39. 'text/html',
  40. 'text/plain',
  41. 'application/json'
  42. ];
  43. var mediaType = {
  44. 'text/html': 'html',
  45. 'text/plain': 'plain',
  46. 'application/json': 'json'
  47. };
  48. /**
  49. * Serve directory listings with the given `root` path.
  50. *
  51. * See Readme.md for documentation of options.
  52. *
  53. * @param {String} path
  54. * @param {Object} options
  55. * @return {Function} middleware
  56. * @api public
  57. */
  58. exports = module.exports = function directory(root, options){
  59. options = options || {};
  60. // root required
  61. if (!root) throw new Error('directory() root path required');
  62. var hidden = options.hidden
  63. , icons = options.icons
  64. , view = options.view || 'tiles'
  65. , filter = options.filter
  66. , root = normalize(root + sep)
  67. , template = options.template || defaultTemplate
  68. , stylesheet = options.stylesheet || defaultStylesheet;
  69. return function directory(req, res, next) {
  70. if ('GET' != req.method && 'HEAD' != req.method) return next();
  71. var url = parse(req.url)
  72. , dir = decodeURIComponent(url.pathname)
  73. , path = normalize(join(root, dir))
  74. , originalUrl = parse(req.originalUrl)
  75. , originalDir = decodeURIComponent(originalUrl.pathname)
  76. , showUp = path != root;
  77. // null byte(s), bad request
  78. if (~path.indexOf('\0')) return next(createError(400));
  79. // malicious path, forbidden
  80. if (0 != path.indexOf(root)) return next(createError(403));
  81. // check if we have a directory
  82. fs.stat(path, function(err, stat){
  83. if (err) return 'ENOENT' == err.code
  84. ? next()
  85. : next(err);
  86. if (!stat.isDirectory()) return next();
  87. // fetch files
  88. fs.readdir(path, function(err, files){
  89. if (err) return next(err);
  90. if (!hidden) files = removeHidden(files);
  91. if (filter) files = files.filter(filter);
  92. files.sort();
  93. // content-negotiation
  94. var type = new Negotiator(req).preferredMediaType(mediaTypes);
  95. // not acceptable
  96. if (!type) return next(createError(406));
  97. exports[mediaType[type]](req, res, files, next, originalDir, showUp, icons, path, view, template, stylesheet);
  98. });
  99. });
  100. };
  101. };
  102. /**
  103. * Respond with text/html.
  104. */
  105. exports.html = function(req, res, files, next, dir, showUp, icons, path, view, template, stylesheet){
  106. fs.readFile(template, 'utf8', function(err, str){
  107. if (err) return next(err);
  108. fs.readFile(stylesheet, 'utf8', function(err, style){
  109. if (err) return next(err);
  110. stat(path, files, function(err, stats){
  111. if (err) return next(err);
  112. files = files.map(function(file, i){ return { name: file, stat: stats[i] }; });
  113. files.sort(fileSort);
  114. if (showUp) files.unshift({ name: '..' });
  115. str = str
  116. .replace('{style}', style.concat(iconStyle(files, icons)))
  117. .replace('{files}', html(files, dir, icons, view))
  118. .replace('{directory}', dir)
  119. .replace('{linked-path}', htmlPath(dir));
  120. res.setHeader('Content-Type', 'text/html');
  121. res.setHeader('Content-Length', str.length);
  122. res.end(str);
  123. });
  124. });
  125. });
  126. };
  127. /**
  128. * Respond with application/json.
  129. */
  130. exports.json = function(req, res, files){
  131. files = JSON.stringify(files);
  132. res.setHeader('Content-Type', 'application/json');
  133. res.setHeader('Content-Length', files.length);
  134. res.end(files);
  135. };
  136. /**
  137. * Respond with text/plain.
  138. */
  139. exports.plain = function(req, res, files){
  140. files = files.join('\n') + '\n';
  141. res.setHeader('Content-Type', 'text/plain');
  142. res.setHeader('Content-Length', files.length);
  143. res.end(files);
  144. };
  145. /**
  146. * Generate an `Error` from the given status `code`
  147. * and optional `msg`.
  148. *
  149. * @param {Number} code
  150. * @param {String} msg
  151. * @return {Error}
  152. * @api private
  153. */
  154. function createError(code, msg) {
  155. var err = new Error(msg || http.STATUS_CODES[code]);
  156. err.status = code;
  157. return err;
  158. };
  159. /**
  160. * Sort function for with directories first.
  161. */
  162. function fileSort(a, b) {
  163. return Number(b.stat && b.stat.isDirectory()) - Number(a.stat && a.stat.isDirectory()) ||
  164. String(a.name).toLocaleLowerCase().localeCompare(String(b.name).toLocaleLowerCase());
  165. }
  166. /**
  167. * Map html `dir`, returning a linked path.
  168. */
  169. function htmlPath(dir) {
  170. var curr = [];
  171. return dir.split('/').map(function(part){
  172. curr.push(encodeURIComponent(part));
  173. return part ? '<a href="' + curr.join('/') + '">' + part + '</a>' : '';
  174. }).join(' / ');
  175. }
  176. /**
  177. * Load icon images, return css string.
  178. */
  179. function iconStyle (files, useIcons) {
  180. if (!useIcons) return '';
  181. var className;
  182. var i;
  183. var icon;
  184. var list = [];
  185. var rules = {};
  186. var selector;
  187. var selectors = {};
  188. var style = '';
  189. for (i = 0; i < files.length; i++) {
  190. var file = files[i];
  191. var isDir = '..' == file.name || (file.stat && file.stat.isDirectory());
  192. icon = isDir ? icons.folder : icons[extname(file.name)] || icons.default;
  193. var ext = extname(file.name);
  194. className = 'icon-' + (isDir ? 'directory' : (icons[ext] ? ext.substring(1) : 'default'));
  195. selector = '#files .' + className + ' .name';
  196. if (!rules[icon]) {
  197. rules[icon] = 'background-image: url(data:image/png;base64,' + load(icon) + ');'
  198. selectors[icon] = [];
  199. list.push(icon);
  200. }
  201. if (!~selectors[icon].indexOf(selector)) {
  202. selectors[icon].push(selector);
  203. }
  204. }
  205. for (i = 0; i < list.length; i++) {
  206. icon = list[i];
  207. style += selectors[icon].join(',\n') + ' {\n ' + rules[icon] + '\n}\n';
  208. }
  209. return style;
  210. }
  211. /**
  212. * Map html `files`, returning an html unordered list.
  213. */
  214. function html(files, dir, useIcons, view) {
  215. return '<ul id="files" class="view-'+view+'">'
  216. + (view == 'details' ? (
  217. '<li class="header">'
  218. + '<span class="name">Name</span>'
  219. + '<span class="size">Size</span>'
  220. + '<span class="date">Modified</span>'
  221. + '</li>') : '')
  222. + files.map(function(file){
  223. var isDir = '..' == file.name || (file.stat && file.stat.isDirectory())
  224. , classes = []
  225. , path = dir.split('/').map(function (c) { return encodeURIComponent(c); });
  226. if (useIcons) {
  227. var ext = extname(file.name);
  228. ext = isDir ? '.directory' : (icons[ext] ? ext : '.default');
  229. classes.push('icon');
  230. classes.push('icon-' + ext.substring(1));
  231. }
  232. path.push(encodeURIComponent(file.name));
  233. var date = file.stat && file.name !== '..'
  234. ? file.stat.mtime.toDateString() + ' ' + file.stat.mtime.toLocaleTimeString()
  235. : '';
  236. var size = file.stat && !isDir
  237. ? file.stat.size
  238. : '';
  239. return '<li><a href="'
  240. + normalizeSlashes(normalize(path.join('/')))
  241. + '" class="'
  242. + classes.join(' ') + '"'
  243. + ' title="' + file.name + '">'
  244. + '<span class="name">'+file.name+'</span>'
  245. + '<span class="size">'+size+'</span>'
  246. + '<span class="date">'+date+'</span>'
  247. + '</a></li>';
  248. }).join('\n') + '</ul>';
  249. }
  250. /**
  251. * Load and cache the given `icon`.
  252. *
  253. * @param {String} icon
  254. * @return {String}
  255. * @api private
  256. */
  257. function load(icon) {
  258. if (cache[icon]) return cache[icon];
  259. return cache[icon] = fs.readFileSync(__dirname + '/public/icons/' + icon, 'base64');
  260. }
  261. /**
  262. * Normalizes the path separator from system separator
  263. * to URL separator, aka `/`.
  264. *
  265. * @param {String} path
  266. * @return {String}
  267. * @api private
  268. */
  269. function normalizeSlashes(path) {
  270. return path.split(sep).join('/');
  271. };
  272. /**
  273. * Filter "hidden" `files`, aka files
  274. * beginning with a `.`.
  275. *
  276. * @param {Array} files
  277. * @return {Array}
  278. * @api private
  279. */
  280. function removeHidden(files) {
  281. return files.filter(function(file){
  282. return '.' != file[0];
  283. });
  284. }
  285. /**
  286. * Stat all files and return array of stat
  287. * in same order.
  288. */
  289. function stat(dir, files, cb) {
  290. var batch = new Batch();
  291. batch.concurrency(10);
  292. files.forEach(function(file){
  293. batch.push(function(done){
  294. fs.stat(join(dir, file), function(err, stat){
  295. if (err && err.code !== 'ENOENT') {
  296. // pass ENOENT as null stat, not error
  297. return done(err);
  298. }
  299. done(null, stat || null);
  300. });
  301. });
  302. });
  303. batch.end(cb);
  304. }
  305. /**
  306. * Icon map.
  307. */
  308. var icons = {
  309. '.js': 'page_white_code_red.png'
  310. , '.json': 'page_white_code.png'
  311. , '.c': 'page_white_c.png'
  312. , '.h': 'page_white_h.png'
  313. , '.cc': 'page_white_cplusplus.png'
  314. , '.php': 'page_white_php.png'
  315. , '.rb': 'page_white_ruby.png'
  316. , '.erb': 'page_white_ruby.png'
  317. , '.cpp': 'page_white_cplusplus.png'
  318. , '.as': 'page_white_actionscript.png'
  319. , '.cfm': 'page_white_coldfusion.png'
  320. , '.cs': 'page_white_csharp.png'
  321. , '.java': 'page_white_cup.png'
  322. , '.jsp': 'page_white_cup.png'
  323. , '.dll': 'page_white_gear.png'
  324. , '.ini': 'page_white_gear.png'
  325. , '.asp': 'page_white_code.png'
  326. , '.aspx': 'page_white_code.png'
  327. , '.clj': 'page_white_code.png'
  328. , '.css': 'page_white_code.png'
  329. , '.sass': 'page_white_code.png'
  330. , '.scss': 'page_white_code.png'
  331. , '.less': 'page_white_code.png'
  332. , '.htm': 'page_white_code.png'
  333. , '.html': 'page_white_code.png'
  334. , '.xhtml': 'page_white_code.png'
  335. , '.lua': 'page_white_code.png'
  336. , '.m': 'page_white_code.png'
  337. , '.pl': 'page_white_code.png'
  338. , '.py': 'page_white_code.png'
  339. , '.vb': 'page_white_code.png'
  340. , '.vbs': 'page_white_code.png'
  341. , '.xml': 'page_white_code.png'
  342. , '.yaws': 'page_white_code.png'
  343. , '.map': 'map.png'
  344. , '.app': 'application_xp.png'
  345. , '.exe': 'application_xp.png'
  346. , '.bat': 'application_xp_terminal.png'
  347. , '.cgi': 'application_xp_terminal.png'
  348. , '.sh': 'application_xp_terminal.png'
  349. , '.avi': 'film.png'
  350. , '.flv': 'film.png'
  351. , '.mv4': 'film.png'
  352. , '.mov': 'film.png'
  353. , '.mp4': 'film.png'
  354. , '.mpeg': 'film.png'
  355. , '.mpg': 'film.png'
  356. , '.ogv': 'film.png'
  357. , '.rm': 'film.png'
  358. , '.webm': 'film.png'
  359. , '.wmv': 'film.png'
  360. , '.fnt': 'font.png'
  361. , '.otf': 'font.png'
  362. , '.ttf': 'font.png'
  363. , '.woff': 'font.png'
  364. , '.bmp': 'image.png'
  365. , '.gif': 'image.png'
  366. , '.ico': 'image.png'
  367. , '.jpeg': 'image.png'
  368. , '.jpg': 'image.png'
  369. , '.png': 'image.png'
  370. , '.psd': 'page_white_picture.png'
  371. , '.xcf': 'page_white_picture.png'
  372. , '.pdf': 'page_white_acrobat.png'
  373. , '.swf': 'page_white_flash.png'
  374. , '.ai': 'page_white_vector.png'
  375. , '.eps': 'page_white_vector.png'
  376. , '.ps': 'page_white_vector.png'
  377. , '.svg': 'page_white_vector.png'
  378. , '.ods': 'page_white_excel.png'
  379. , '.xls': 'page_white_excel.png'
  380. , '.xlsx': 'page_white_excel.png'
  381. , '.odp': 'page_white_powerpoint.png'
  382. , '.ppt': 'page_white_powerpoint.png'
  383. , '.pptx': 'page_white_powerpoint.png'
  384. , '.md': 'page_white_text.png'
  385. , '.srt': 'page_white_text.png'
  386. , '.txt': 'page_white_text.png'
  387. , '.doc': 'page_white_word.png'
  388. , '.docx': 'page_white_word.png'
  389. , '.odt': 'page_white_word.png'
  390. , '.rtf': 'page_white_word.png'
  391. , '.dmg': 'drive.png'
  392. , '.iso': 'cd.png'
  393. , '.7z': 'box.png'
  394. , '.apk': 'box.png'
  395. , '.bz2': 'box.png'
  396. , '.cab': 'box.png'
  397. , '.deb': 'box.png'
  398. , '.gz': 'box.png'
  399. , '.jar': 'box.png'
  400. , '.lz': 'box.png'
  401. , '.lzma': 'box.png'
  402. , '.msi': 'box.png'
  403. , '.pkg': 'box.png'
  404. , '.rar': 'box.png'
  405. , '.rpm': 'box.png'
  406. , '.tar': 'box.png'
  407. , '.tbz2': 'box.png'
  408. , '.tgz': 'box.png'
  409. , '.tlz': 'box.png'
  410. , '.xz': 'box.png'
  411. , '.zip': 'box.png'
  412. , '.accdb': 'page_white_database.png'
  413. , '.db': 'page_white_database.png'
  414. , '.dbf': 'page_white_database.png'
  415. , '.mdb': 'page_white_database.png'
  416. , '.pdb': 'page_white_database.png'
  417. , '.sql': 'page_white_database.png'
  418. , '.gam': 'controller.png'
  419. , '.rom': 'controller.png'
  420. , '.sav': 'controller.png'
  421. , 'folder': 'folder.png'
  422. , 'default': 'page_white.png'
  423. };