2 events = require('events'),
5 path = require('path');
7 exports.filepath = function (webroot, url) {
8 // Unescape URL to prevent security holes
9 url = decodeURIComponent(url);
10 // Append index.html if path ends with '/'
11 fp = path.normalize(path.join(webroot, (url.match(/\/$/)=='/') ? url+'index.html' : url));
12 // Sanitize input, make sure people can't use .. to get above webroot
13 if (webroot[webroot.length - 1] !== '/') webroot += '/';
14 if (fp.substr(0, webroot.length) != webroot)
15 return(['Permission Denied', null]);
20 exports.streamFile = function (filepath, headerFields, stat, res, req, emitter) {
22 emitter = new events.EventEmitter(),
23 extension = filepath.split('.').pop(),
24 contentType = exports.contentTypes[extension] || 'application/octet-stream',
25 charset = exports.charsets[contentType];
27 process.nextTick( function() {
29 contentType += '; charset=' + charset;
30 headerFields['Content-Type'] = contentType;
32 var etag = '"' + stat.ino + '-' + stat.size + '-' + Date.parse(stat.mtime) +'"';
33 headerFields['ETag'] = etag;
36 //Check to see if we can send a 304 and skip the send
37 if(req.headers['if-none-match'] == etag){
39 headerFields['Content-Length'] = 0;
41 headerFields['Content-Length'] = stat.size;
43 if (headerFields['Expires'] != undefined) {
44 var expires = new Date;
45 expires.setTime(expires.getTime() + headerFields['Expires']);
46 headerFields['Expires'] = expires.toUTCString();
50 res.writeHead(statCode, headerFields);
52 //If we sent a 304, skip sending a body
53 if (statCode == 304 || req.method === 'HEAD') {
55 emitter.emit("success", statCode);
58 fs.createReadStream(filepath,{'flags': 'r', 'encoding':
59 'binary', 'mode': 0666, 'bufferSize': 4 * 1024})
60 .addListener("data", function(chunk){
61 res.write(chunk, 'binary');
63 .addListener("end", function(){
64 emitter.emit("success", statCode);
66 .addListener("close",function() {
69 .addListener("error", function (e) {
70 emitter.emit("error", 500, e);
77 exports.deliver = function (webroot, req, res) {
80 fpRes = exports.filepath(webroot, url.parse(req.url).pathname),
90 error: function (callback) {
91 errorCallback = callback;
94 before: function (callback) {
95 beforeCallback = callback;
98 after: function (callback) {
99 afterCallback = callback;
102 otherwise: function (callback) {
103 otherwiseCallback = callback;
106 addHeader: function (name, value) {
107 headerFields[name] = value;
112 process.nextTick(function() {
113 // Create default error and otherwise callbacks if none were given.
114 errorCallback = errorCallback || function(statCode) {
115 res.writeHead(statCode, {'Content-Type': 'text/html'});
116 res.end("<h1>HTTP " + statCode + "</h1>");
118 otherwiseCallback = otherwiseCallback || function() {
119 res.writeHead(404, {'Content-Type': 'text/html'});
120 res.end("<h1>HTTP 404 File not found</h1>");
123 //If file is in a directory outside of the webroot, deny the request
128 errorCallback(403, 'Forbidden');
131 fs.stat(filepath, function (err, stat) {
132 if( (err || !stat.isFile())) {
133 var exactErr = err || 'File not found';
136 if (otherwiseCallback)
137 otherwiseCallback(exactErr);
139 //The before callback can abort the transfer by returning false
140 var cancel = beforeCallback && (beforeCallback() === false);
141 if (cancel && otherwiseCallback) {
145 stream = exports.streamFile(filepath, headerFields, stat, res, req)
148 stream.addListener("success", afterCallback);
151 stream.addListener("error", errorCallback);
162 exports.contentTypes = {
163 "aiff": "audio/x-aiff",
164 "arj": "application/x-arj-compressed",
165 "asf": "video/x-ms-asf",
166 "asx": "video/x-ms-asx",
168 "avi": "video/x-msvideo",
169 "bcpio": "application/x-bcpio",
170 "ccad": "application/clariscad",
171 "cod": "application/vnd.rim.cod",
172 "com": "application/x-msdos-program",
173 "cpio": "application/x-cpio",
174 "cpt": "application/mac-compactpro",
175 "csh": "application/x-csh",
177 "deb": "application/x-debian-package",
179 "doc": "application/msword",
180 "drw": "application/drafting",
181 "dvi": "application/x-dvi",
182 "dwg": "application/acad",
183 "dxf": "application/dxf",
184 "dxr": "application/x-director",
185 "etx": "text/x-setext",
186 "ez": "application/andrew-inset",
187 "fli": "video/x-fli",
188 "flv": "video/x-flv",
191 "gtar": "application/x-gtar",
192 "gz": "application/x-gzip",
193 "hdf": "application/x-hdf",
194 "hqx": "application/mac-binhex40",
196 "ice": "x-conference/x-cooltalk",
199 "ips": "application/x-ipscript",
200 "ipx": "application/x-ipix",
201 "jad": "text/vnd.sun.j2me.app-descriptor",
202 "jar": "application/java-archive",
203 "jpeg": "image/jpeg",
205 "js": "text/javascript",
206 "json": "application/json",
207 "latex": "application/x-latex",
208 "lsp": "application/x-lisp",
209 "lzh": "application/octet-stream",
211 "m3u": "audio/x-mpegurl",
212 "man": "application/x-troff-man",
213 "me": "application/x-troff-me",
214 "midi": "audio/midi",
215 "mif": "application/x-mif",
217 "movie": "video/x-sgi-movie",
220 "mpga": "audio/mpeg",
221 "ms": "application/x-troff-ms",
222 "nc": "application/x-netcdf",
223 "oda": "application/oda",
224 "ogm": "application/ogg",
225 "pbm": "image/x-portable-bitmap",
226 "pdf": "application/pdf",
227 "pgm": "image/x-portable-graymap",
228 "pgn": "application/x-chess-pgn",
229 "pgp": "application/pgp",
230 "pm": "application/x-perl",
232 "pnm": "image/x-portable-anymap",
233 "ppm": "image/x-portable-pixmap",
234 "ppz": "application/vnd.ms-powerpoint",
235 "pre": "application/x-freelance",
236 "prt": "application/pro_eng",
237 "ps": "application/postscript",
238 "qt": "video/quicktime",
239 "ra": "audio/x-realaudio",
240 "rar": "application/x-rar-compressed",
241 "ras": "image/x-cmu-raster",
242 "rgb": "image/x-rgb",
243 "rm": "audio/x-pn-realaudio",
244 "rpm": "audio/x-pn-realaudio-plugin",
246 "rtx": "text/richtext",
247 "scm": "application/x-lotusscreencam",
248 "set": "application/set",
250 "sh": "application/x-sh",
251 "shar": "application/x-shar",
252 "silo": "model/mesh",
253 "sit": "application/x-stuffit",
254 "skt": "application/x-koan",
255 "smil": "application/smil",
256 "snd": "audio/basic",
257 "sol": "application/solids",
258 "spl": "application/x-futuresplash",
259 "src": "application/x-wais-source",
260 "stl": "application/SLA",
261 "stp": "application/STEP",
262 "sv4cpio": "application/x-sv4cpio",
263 "sv4crc": "application/x-sv4crc",
264 "svg": "image/svg+xml",
265 "swf": "application/x-shockwave-flash",
266 "tar": "application/x-tar",
267 "tcl": "application/x-tcl",
268 "tex": "application/x-tex",
269 "texinfo": "application/x-texinfo",
270 "tgz": "application/x-tar-gz",
271 "tiff": "image/tiff",
272 "tr": "application/x-troff",
273 "tsi": "audio/TSP-audio",
274 "tsp": "application/dsptype",
275 "tsv": "text/tab-separated-values",
277 "unv": "application/i-deas",
278 "ustar": "application/x-ustar",
279 "vcd": "application/x-cdlink",
280 "vda": "application/vda",
281 "vivo": "video/vnd.vivo",
282 "vrm": "x-world/x-vrml",
283 "wav": "audio/x-wav",
284 "wax": "audio/x-ms-wax",
285 "wma": "audio/x-ms-wma",
286 "wmv": "video/x-ms-wmv",
287 "wmx": "video/x-ms-wmx",
289 "wvx": "video/x-ms-wvx",
290 "xbm": "image/x-xbitmap",
291 "xlw": "application/vnd.ms-excel",
293 "xpm": "image/x-xpixmap",
294 "xwd": "image/x-xwindowdump",
295 "xyz": "chemical/x-pdb",
296 "zip": "application/zip"
300 'text/javascript': 'UTF-8',