UNPKG

3.85 kBJavaScriptView Raw
1/*!
2 * file.js - file middleware for bweb
3 * Copyright (c) 2017, Christopher Jeffrey (MIT License).
4 * https://github.com/bcoin-org/bweb
5 */
6
7'use strict';
8
9const assert = require('bsert');
10const fs = require('fs');
11const Path = require('path');
12
13/**
14 * Static file middleware.
15 * @param {String} prefix
16 * @returns {Function}
17 */
18
19function fileServer(prefix) {
20 assert(typeof prefix === 'string');
21
22 return async (req, res) => {
23 if (req.method !== 'GET' && req.method !== 'HEAD')
24 return;
25
26 const file = Path.join(prefix, req.pathname);
27
28 let stat = null;
29
30 try {
31 stat = await fsStat(file);
32 } catch (e) {
33 if (e.code === 'ENOENT')
34 return;
35 throw e;
36 }
37
38 if (stat.isDirectory()) {
39 const title = req.pathname;
40 const body = await dir2html(file, title, req.prefix());
41 res.send(200, body, 'html');
42 return;
43 }
44
45 try {
46 await res.sendFile(file);
47 } catch (e) {
48 throw wrapError(e);
49 }
50 };
51}
52
53/*
54 * Helpers
55 */
56
57function fsStat(file) {
58 return new Promise((resolve, reject) => {
59 fs.stat(file, (err, result) => {
60 if (err) {
61 reject(wrapError(err));
62 return;
63 }
64 resolve(result);
65 });
66 });
67}
68
69function fsReaddir(file) {
70 return new Promise((resolve, reject) => {
71 fs.readdir(file, (err, result) => {
72 if (err) {
73 reject(wrapError(err));
74 return;
75 }
76 resolve(result);
77 });
78 });
79}
80
81function wrapError(e) {
82 if (!e.code) {
83 const err = new Error('Internal server error.');
84 err.statusCode = 500;
85 return err;
86 }
87
88 switch (e.code) {
89 case 'ENOENT':
90 case 'ENAMETOOLONG':
91 case 'ENOTDIR':
92 case 'EISDIR': {
93 const err = new Error('File not found.');
94 err.code = e.code;
95 err.syscall = e.syscall;
96 err.statusCode = 404;
97 return err;
98 }
99 case 'EACCES':
100 case 'EPERM': {
101 const err = new Error('Cannot access file.');
102 err.code = e.code;
103 err.syscall = e.syscall;
104 err.statusCode = 403;
105 return err;
106 }
107 case 'EMFILE': {
108 const err = new Error('Too many open files.');
109 err.code = e.code;
110 err.syscall = e.syscall;
111 err.statusCode = 500;
112 return err;
113 }
114 default: {
115 const err = new Error('Cannot access file.');
116 err.code = e.code;
117 err.syscall = e.syscall;
118 err.statusCode = 500;
119 return err;
120 }
121 }
122}
123
124function escapeHTML(html) {
125 return html
126 .replace(/&/g, '&')
127 .replace(/</g, '&lt;')
128 .replace(/>/g, '&gt;')
129 .replace(/"/g, '&quot;')
130 .replace(/'/g, '&#39;');
131}
132
133async function dir2html(parent, title, prefix) {
134 let body = '';
135
136 title = escapeHTML(title);
137 prefix = escapeHTML(prefix);
138
139 body += '<!DOCTYPE html>\n';
140 body += '<html lang="en">\n';
141 body += ' <head>\n';
142 body += ` <title>Index of ${title}</title>\n`;
143 body += ' <meta charset="utf-8">\n';
144 body += ' </head>\n';
145 body += ' <body>\n';
146 body += ` <p>Index of ${title}</p>\n`;
147 body += ' <ul>\n';
148 body += ` <li><a href="${prefix}..">../</a></li>\n`;
149
150 const list = await fsReaddir(parent);
151 const dirs = [];
152 const files = [];
153
154 for (const file of list) {
155 const path = Path.join(parent, file);
156 const stat = await fsStat(path);
157
158 if (stat.isDirectory())
159 dirs.push(file);
160 else
161 files.push(file);
162 }
163
164 for (const file of dirs.sort()) {
165 const name = escapeHTML(file);
166 const href = `${prefix}${name}`;
167 body += ` <li><a href="${href}">${name}/</a></li>\n`;
168 }
169
170 for (const file of files.sort()) {
171 const name = escapeHTML(file);
172 const href = `${prefix}${name}`;
173 body += ` <li><a href="${href}">${name}</a></li>\n`;
174 }
175
176 body += ' </ul>\n';
177 body += ' </body>\n';
178 body += '</html>\n';
179
180 return body;
181}
182
183/*
184 * Expose
185 */
186
187module.exports = fileServer;