node-http-server – Blame information for rev 11

Subversion Repositories:
Rev:
Rev Author Line No. Line
8 office 1 #!/usr/bin/env node
2  
9 office 3 /*************************************************************************/
4 /* Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3 */
5 /*************************************************************************/
6  
8 office 7 const url = require('url');
8 const path = require('path');
9 const fs = require('fs');
10 const mime = require('mime');
11  
11 office 12 // Checks whether userPath is a child of rootPath
8 office 13 function isRooted(userPath, rootPath, separator) {
14 userPath = userPath.split(separator).filter(Boolean);
15 rootPath = rootPath.split(separator).filter(Boolean);
16 return userPath.length >= rootPath.length &&
17 rootPath.every((e, i) => e === userPath[i]);
18 }
19  
20 module.exports = {
21 error: {
22 level: {
23 INFO: 1,
24 WARN: 2,
25 ERROR: 3
26 }
27 },
11 office 28 process: (config, request, response, root, callback) => {
8 office 29 process.nextTick(() => {
30 const requestAddress = request.socket.address();
31 const requestedURL = url.parse(request.url, true);
32  
11 office 33 process.nextTick(() => {
34 callback('Client: ' +
35 requestAddress.address + ':' +
36 requestAddress.port +
37 ' accessing: ' +
38 requestedURL.pathname,
39 module.exports.error.level.INFO
40 );
41 });
8 office 42  
43 const trimmedPath = requestedURL
44 .pathname
45 .split('/')
46 .filter(Boolean)
47 .join('/');
48 const filesystemPath = trimmedPath === '/' ?
49 path.join(root, trimmedPath) :
50 path.resolve(root, trimmedPath);
51  
52 if (!isRooted(filesystemPath, root, path.sep)) {
11 office 53 process.nextTick(() => {
54 callback('Attempted path traversal: ' +
55 requestAddress.address + ':' +
56 requestAddress.port +
57 ' requesting: ' +
58 requestedURL.pathname,
59 module.exports.error.level.WARN
60 );
61 });
8 office 62 response.statusCode = 403;
63 response.end();
64 return;
65 }
66  
67 fs.stat(filesystemPath, (error, stats) => {
68 // Document does not exist.
69 if (error) {
70 response.statusCode = 404;
71 response.end();
72 return;
73 }
74  
75 switch (stats.isDirectory()) {
76 case true: // Directory is requested so provide directory indexes.
77 const root = path.resolve(filesystemPath, config.site.index);
78 fs.stat(root, (error, stats) => {
79 if (error) {
80 fs.readdir(filesystemPath, (error, paths) => {
81 if (error) {
11 office 82 process.nextTick(() => {
83 callback('Could not list directory: ' +
84 filesystemPath,
85 module.exports.error.level.ERROR
86 );
87 });
8 office 88 response.statusCode = 500;
89 response.end();
90 return;
91 }
11 office 92 process.nextTick(() => {
93 callback('Directory listing requested for: ' +
94 filesystemPath,
95 module.exports.error.level.INFO
96 );
97 });
8 office 98 response.statusCode = 200;
99 response.write(JSON.stringify(paths));
100 response.end();
101 });
102  
103 return;
104 }
105  
106 fs.access(filesystemPath, fs.constants.R_OK, (error) => {
107 if (error) {
11 office 108 process.nextTick(() => {
109 callback('The server was unable to access the filesystem path: ' +
110 filesystemPath,
111 module.exports.error.level.WARN
112 );
113 });
8 office 114 response.statusCode = 403;
115 response.end();
116 return;
117 }
118  
119 // Set MIME content type.
120 response.setHeader('Content-Type', mime.lookup(root));
121  
122 var readStream = fs.createReadStream(root)
123 .on('open', () => {
124 response.statusCode = 200;
125 readStream.pipe(response);
126 })
127 .on('error', () => {
128 response.statusCode = 500;
129 response.end();
130 });
131  
132 });
133  
134 });
135 break;
136 default: // Browser requesting file.
137 // Check if the file is accessible.
138 fs.access(filesystemPath, fs.constants.R_OK, (error) => {
139 if (error) {
140 response.statusCode = 403;
141 response.end();
142 return;
143 }
144  
145 response.setHeader('Content-Type', mime.lookup(filesystemPath));
146  
147 var readStream = fs.createReadStream(filesystemPath)
148 .on('open', () => {
149 response.statusCode = 200;
150 readStream.pipe(response);
151 })
152 .on('error', () => {
153 response.statusCode = 500;
154 response.end();
155 });
156  
157 });
158 break;
159 }
160 })
161 });
162 }
163 };