UNPKG

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
/* * 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 }