This repository has been archived by the owner on Mar 5, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
200 lines (176 loc) · 4.87 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
/**
* Testing server that serves any files inside its docroot and resolves
* embedded Javascript (EJS) and ESI includes inside HTML files (much
* like a PHP server would parse and execute PHP).
*
* It also offers a very minimalistic directory browsing feature for
* simplifying local development of fixtures.
*
* (c) 2018 Rico Pfaus <http://github.com/ryx/fixtureserver>
*/
const Hapi = require('hapi');
const ESI = require('nodesi');
const fs = require('fs');
const path = require('path');
const ejs = require('ejs');
// docroot
let serverDocroot = path.join(__dirname, '/docroot');
// create ESI processor instance
const esi = new ESI();
/**
* Converts a given path- or filename to a path relative to the server docroot
* (i.e. strips leading docroot).
* @param {String} pathName the path to be relativized
*/
function relativizePathName(pathName) {
return pathName.replace(new RegExp(`^${serverDocroot.replace(/\//, '\\/')}`, 'gi'), '');
}
/**
* Converts a given path- or filename to an absolute path (i.e. prepends docroot).
* @param {String} pathName the path to be absolutized
*/
function absolutizePathName(pathName) {
if (pathName.match(new RegExp(`^${serverDocroot.replace(/\//, '\\/')}`, 'gi'))) {
return pathName;
}
return path.join(serverDocroot, pathName);
}
/**
* Returns the parent directory for the given path or file.
* @param {String} pathName path- or filename to get the parent for
*/
function getParentDirectory(pathName) {
const parts = pathName.split('/');
if (parts.length === 1) {
return '/';
}
parts.pop();
return parts.join('/');
}
function renderSystemPageFrame(html) {
return `<!doctype html>
<html>
<head>
<style>
body {
font-size: 14px;
font-family: monospace;
line-height: 1.4rem;
}
ul {
list-style: none;
}
</style>
</head>
<body>${html}</body>
</html>
`;
}
/**
* Render a directory tree for the given pathName.
* @param {String} pathName the pathName (relative the server docroot)
*/
async function renderPath(pathName) {
const absolutePath = absolutizePathName(pathName);
const relativePath = relativizePathName(pathName);
let html = `<li><a href="${getParentDirectory(relativePath)}">/..</a></li>`;
const files = fs.readdirSync(absolutePath);
if (!files) {
throw new Error(`Path listing failed for: ${pathName}`);
}
files.forEach((file) => {
const absoluteFile = path.join(absolutePath, file);
const stats = fs.statSync(absoluteFile);
if (!stats) {
throw new Error(`Stats failed for: ${absoluteFile}`);
}
html += `<li><a href="${relativePath}/${file}">/${file}${stats.isDirectory() ? '/' : ''}</a></li>`;
});
return renderSystemPageFrame(`<ul>${html}</ul>`);
}
/**
* Render the given file and return it's HTML.
* @param {String} fileName
*/
async function renderFile(fileName) {
let html = '';
// load file data
const fileContent = fs.readFileSync(fileName);
html = '';
if (fileContent) {
html = fileContent.toString();
}
// pass it through ejs (@FIXME: handle errors)
html = ejs.render(html, {
// virtual data
server: {
version: '0.0.1',
dirname: serverDocroot,
},
require,
}, {
// EJS options
filename: fileName,
});
// pass it through ESI processor to resolve remote includes
html = await esi.process(html);
if (!html) {
throw new Error(`ESI processing failed for: ${fileName}`);
}
return html;
}
// Start the server
async function start(options) {
// Create a server with a host and port
const server = Hapi.server({
host: options.host || 'localhost',
port: options.port || 8000,
});
// override docroot if set
if (options.docroot) {
serverDocroot = options.docroot;
}
// add routes
server.route({
method: 'GET',
path: '/{filename*}',
async handler(request, h) {
try {
let html = '';
const targetFile = path.join(serverDocroot, request.params.filename);
console.log(`Requested: ${targetFile}`);
// check existence
const stats = fs.statSync(targetFile);
if (!stats) {
h.status = 404;
return h.response('server-esi: file not found');
}
// render contents
if (stats.isDirectory()) {
html = await renderPath(request.params.filename);
} else {
html = await renderFile(targetFile);
}
// return result
h.status = 200;
return h.response(html);
} catch (e) {
// bulk handle all errors
h.status = 500;
return h.response(e.message);
}
},
});
// launch server
try {
await server.start();
} catch (err) {
console.log(err);
process.exit(1);
}
console.log('IMPORTANT: this server likely contains security errors and is NOT intended for production use!');
console.log('Server running at:', server.info.uri);
}
module.exports = {
start,
};