lime-static
Version:
Static file server for node.js REST servers using OWIN-JS framework for Project limerun
178 lines (146 loc) • 6.11 kB
JavaScript
/*
* Copyright 2015 Domabo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Module dependencies.
*/
var fs = require('fs');
var path = require('path');
var Promise = require('bluebird');
var normalize = path.normalize;
var basename = path.basename;
var extname = path.extname;
var pathResolve = path.resolve;
var join = path.join;
var mime = require('mime');
var util = require('util');
var statAsync = Promise.promisify(fs.stat);
/**
* Expose `send()`.
*/
module.exports = owinStaticSend;
/**
* Send file at `path` with the
* given `options` to the owin/js `owin` context.
*
* @param {Context} owin
* @param {String} path
* @param {Object} [opts]
* @return {Function}
* @api public
*/
function owinStaticSend(owin, path, opts) {
opts = opts || {};
return new Promise(function owinStaticLoad(resolve, reject){
var root = opts.root ? normalize(pathResolve(opts.root)) : '';
var index = opts.index;
var maxage = opts.maxage || 0;
var hidden = opts.hidden || false;
var sync = opts.sync || false;
opts = null;
var trailingSlash = '/' == path[path.length - 1];
// normalize path
path = decode(path);
if (-1 == path) return reject('failed to decode');
// null byte(s)
if (~path.indexOf('\0')) return reject('null bytes');
// index file support
if (index && trailingSlash) path += index;
// malicious path
if (!root && !isAbsolute(path)) return reject('relative paths require the .root option');
if (!root && ~path.indexOf('..')) return reject('malicious path');
// relative to root
path = normalize(join(root, path));
// out of bounds
if (root && 0 != path.indexOf(root)) return reject('malicious path');
// hidden file support, ignore
if (!hidden && leadingDot(path)) return resolve();
var stats;
try
{
stats = fs.statSync(path);
}
catch (err) {
return resolve(null);
}
if (stats.isDirectory())
{
return resolve(null);
}
var contentType = mime.lookup(path) || 'application/octet-stream';
owin.response.writeHead(200, {
'Content-Type' : contentType,
'Last-Modified' : stats.mtime.toUTCString(),
'Content-Length': stats.size + '',
'Cache-Control': 'max-age=' + (maxage / 1000 | 0)});
if (sync)
{
var bodyBuffer = fs.readFileSync(path);
owin.response.end(bodyBuffer);
bodyBuffer = null;
owin = null;
return resolve();
}
else
{
var stream = fs.createReadStream(path, { flags: 'r',
encoding: null,
autoClose: true
});
stream.on('error', function(err){
console.log(err);
stream = null;
owin = null;
reject(err);
});
stream.on('end', function(){
owin.response.end();
stream = null;
owin = null;
resolve();
});
stream.pipe(owin["owin.ResponseBody"]);
return;
}
});
}
/**
* Check if it's hidden.
*/
function leadingDot(path) {
return '.' == basename(path)[0];
}
/**
* Decode `path`.
*/
function decode(path) {
try {
return decodeURIComponent(path);
} catch (err) {
return -1;
}
}
/**
* Check if `path` looks absolute.
*
* @param {String} path
* @return {Boolean}
* @api private
*/
function isAbsolute(path){
if ('/' == path[0]) return true;
if (':' == path[1] && '\\' == path[2]) return true;
if ('\\\\' == path.substring(0, 2)) return true; // Microsoft Azure absolute path
}