arnaucube
arnaucube/socketioMEANseedPublic Self-hosted repository
socketioMEANseed/www/node_modules/ecstatic/lib/ecstatic.js
www/node_modules/ecstatic/lib/ecstatic.jsRaw
  1#! /usr/bin/env node
  2
  3var path = require('path'),
  4    fs = require('fs'),
  5    url = require('url'),
  6    mime = require('mime'),
  7    urlJoin = require('url-join'),
  8    showDir = require('./ecstatic/showdir'),
  9    version = JSON.parse(
 10      fs.readFileSync(__dirname + '/../package.json').toString()
 11    ).version,
 12    status = require('./ecstatic/status-handlers'),
 13    generateEtag = require('./ecstatic/etag'),
 14    optsParser = require('./ecstatic/opts');
 15
 16var ecstatic = module.exports = function (dir, options) {
 17  if (typeof dir !== 'string') {
 18    options = dir;
 19    dir = options.root;
 20  }
 21
 22  var root = path.join(path.resolve(dir), '/'),
 23      opts = optsParser(options),
 24      cache = opts.cache,
 25      autoIndex = opts.autoIndex,
 26      baseDir = opts.baseDir,
 27      defaultExt = opts.defaultExt,
 28      handleError = opts.handleError,
 29      headers = opts.headers,
 30      serverHeader = opts.serverHeader,
 31      weakEtags = opts.weakEtags,
 32      handleOptionsMethod = opts.handleOptionsMethod;
 33
 34  opts.root = dir;
 35  if (defaultExt && /^\./.test(defaultExt)) defaultExt = defaultExt.replace(/^\./, '');
 36
 37  // Support hashes and .types files in mimeTypes @since 0.8
 38  if (opts.mimeTypes) {
 39    try {
 40      // You can pass a JSON blob here---useful for CLI use
 41      opts.mimeTypes = JSON.parse(opts.mimeTypes);
 42    } catch (e) {}
 43    if (typeof opts.mimeTypes === 'string') {
 44      mime.load(opts.mimeTypes);
 45    }
 46    else if (typeof opts.mimeTypes === 'object') {
 47      mime.define(opts.mimeTypes);
 48    }
 49  }
 50
 51
 52  return function middleware (req, res, next) {
 53
 54    // Strip any null bytes from the url
 55    while(req.url.indexOf('%00') !== -1) {
 56      req.url = req.url.replace(/\%00/g, '');
 57    }
 58    // Figure out the path for the file from the given url
 59    var parsed = url.parse(req.url);
 60    try {
 61      decodeURIComponent(req.url); // check validity of url
 62      var pathname = decodePathname(parsed.pathname);
 63    }
 64    catch (err) {
 65      return status[400](res, next, { error: err });
 66    }
 67
 68    var file = path.normalize(
 69          path.join(root,
 70            path.relative(
 71              path.join('/', baseDir),
 72              pathname
 73            )
 74          )
 75        ),
 76        gzipped = file + '.gz';
 77
 78    if(serverHeader !== false) {
 79      // Set common headers.
 80      res.setHeader('server', 'ecstatic-'+version);
 81    }
 82    Object.keys(headers).forEach(function (key) {
 83      res.setHeader(key, headers[key])
 84    })
 85
 86    if (req.method === 'OPTIONS' && handleOptionsMethod) {
 87      return res.end();
 88    }
 89
 90    // TODO: This check is broken, which causes the 403 on the
 91    // expected 404.
 92    if (file.slice(0, root.length) !== root) {
 93      return status[403](res, next);
 94    }
 95
 96    if (req.method && (req.method !== 'GET' && req.method !== 'HEAD' )) {
 97      return status[405](res, next);
 98    }
 99
100    function statFile() {
101      fs.stat(file, function (err, stat) {
102        if (err && (err.code === 'ENOENT' || err.code === 'ENOTDIR')) {
103          if (req.statusCode == 404) {
104            // This means we're already trying ./404.html and can not find it.
105            // So send plain text response with 404 status code
106            status[404](res, next);
107          }
108          else if (!path.extname(parsed.pathname).length && defaultExt) {
109            // If there is no file extension in the path and we have a default
110            // extension try filename and default extension combination before rendering 404.html.
111            middleware({
112              url: parsed.pathname + '.' + defaultExt + ((parsed.search)? parsed.search:'')
113            }, res, next);
114          }
115          else {
116            // Try to serve default ./404.html
117            middleware({
118              url: (handleError ? ('/' + path.join(baseDir, '404.' + defaultExt)) : req.url),
119              statusCode: 404
120            }, res, next);
121          }
122        }
123        else if (err) {
124          status[500](res, next, { error: err });
125        }
126        else if (stat.isDirectory()) {
127          // 302 to / if necessary
128          if (!parsed.pathname.match(/\/$/)) {
129            res.statusCode = 302;
130            res.setHeader('location', parsed.pathname + '/' +
131              (parsed.query? ('?' + parsed.query):'')
132            );
133            return res.end();
134          }
135
136          if (autoIndex) {
137            return middleware({
138              url: urlJoin(encodeURIComponent(pathname), '/index.' + defaultExt)
139            }, res, function (err) {
140              if (err) {
141                return status[500](res, next, { error: err });
142              }
143              if (opts.showDir) {
144                return showDir(opts, stat)(req, res);
145              }
146
147              return status[403](res, next);
148            });
149          }
150
151          if (opts.showDir) {
152            return showDir(opts, stat)(req, res);
153          }
154
155          status[404](res, next);
156
157        }
158        else {
159          serve(stat);
160        }
161      });
162    }
163
164    // Look for a gzipped file if this is turned on
165    if (opts.gzip && shouldCompress(req)) {
166      fs.stat(gzipped, function (err, stat) {
167        if (!err && stat.isFile()) {
168          file = gzipped;
169          return serve(stat);
170        } else {
171          statFile();
172        }
173      });
174    } else {
175      statFile();
176    }
177
178    function serve(stat) {
179      // Do a MIME lookup, fall back to octet-stream and handle gzip
180      // special case.
181      var defaultType = opts.contentType || 'application/octet-stream',
182          contentType = mime.lookup(file, defaultType),
183          charSet;
184
185      if (contentType) {
186        charSet = mime.charsets.lookup(contentType, 'utf-8');
187        if (charSet) {
188          contentType += '; charset=' + charSet;
189        }
190      }
191
192      if (path.extname(file) === '.gz') {
193        res.setHeader('Content-Encoding', 'gzip');
194
195        // strip gz ending and lookup mime type
196        contentType = mime.lookup(path.basename(file, ".gz"), defaultType);
197      }
198
199      var range = (req.headers && req.headers['range']);
200      if (range) {
201        var total = stat.size;
202        var parts = range.replace(/bytes=/, "").split("-");
203        var partialstart = parts[0];
204        var partialend = parts[1];
205        var start = parseInt(partialstart, 10);
206        var end = Math.min(total-1, partialend ? parseInt(partialend, 10) : total-1);
207        var chunksize = (end-start)+1;
208        if (start > end || isNaN(start) || isNaN(end)) {
209          return status['416'](res, next);
210        }
211        var fstream = fs.createReadStream(file, {start: start, end: end});
212        fstream.on('error', function (err) {
213          status['500'](res, next, { error: err });
214        });
215        res.on('close', function () {
216           fstream.destroy();
217        });
218        res.writeHead(206, {
219          'Content-Range': 'bytes ' + start + '-' + end + '/' + total,
220          'Accept-Ranges': 'bytes',
221          'Content-Length': chunksize,
222          'Content-Type': contentType
223        });
224        fstream.pipe(res);
225        return;
226      }
227
228      // TODO: Helper for this, with default headers.
229      var lastModified = (new Date(stat.mtime)).toUTCString(),
230          etag = generateEtag(stat, weakEtags);
231      res.setHeader('last-modified', lastModified);
232      res.setHeader('etag', etag);
233
234      if (typeof cache === 'function') {
235        var requestSpecificCache = cache(pathname);
236        if (typeof requestSpecificCache === 'number') {
237          requestSpecificCache = 'max-age=' + requestSpecificCache;
238        }
239        res.setHeader('cache-control', requestSpecificCache);
240      } else {
241        res.setHeader('cache-control', cache);
242      }
243
244      // Return a 304 if necessary
245      if (shouldReturn304(req, lastModified, etag)) {
246        return status[304](res, next);
247      }
248
249      res.setHeader('content-length', stat.size);
250      res.setHeader('content-type', contentType);
251
252      // set the response statusCode if we have a request statusCode.
253      // This only can happen if we have a 404 with some kind of 404.html
254      // In all other cases where we have a file we serve the 200
255      res.statusCode = req.statusCode || 200;
256
257      if (req.method === "HEAD") {
258        return res.end();
259      }
260
261      var stream = fs.createReadStream(file);
262
263      stream.pipe(res);
264      stream.on('error', function (err) {
265        status['500'](res, next, { error: err });
266      });
267    }
268
269    function shouldReturn304(req, serverLastModified, serverEtag) {
270      if (!req || !req.headers) {
271        return false;
272      }
273
274      var clientModifiedSince = req.headers['if-modified-since'],
275          clientEtag = req.headers['if-none-match'];
276
277      if (!clientModifiedSince && !clientEtag) {
278        // Client did not provide any conditional caching headers
279        return false;
280      }
281
282      if (clientModifiedSince) {
283        // Catch "illegal access" dates that will crash v8
284        // https://github.com/jfhbrook/node-ecstatic/pull/179
285        try {
286          var clientModifiedDate = new Date(Date.parse(clientModifiedSince));
287        }
288        catch (err) { return false }
289
290        if (clientModifiedDate.toString() === 'Invalid Date') {
291          return false;
292        }
293        // If the client's copy is older than the server's, don't return 304
294        if (clientModifiedDate < new Date(serverLastModified)) {
295          return false;
296        }
297      }
298
299      if (clientEtag) {
300        // Do a strong or weak etag comparison based on setting
301        // https://www.ietf.org/rfc/rfc2616.txt Section 13.3.3
302        if (opts.weakCompare && clientEtag !== serverEtag
303          && clientEtag !== ('W/' + serverEtag) && ('W/' + clientEtag) !== serverEtag) {
304          return false;
305        } else if (!opts.weakCompare && (clientEtag !== serverEtag || clientEtag.indexOf('W/') === 0)) {
306          return false;
307        }
308      }
309
310      return true;
311    }
312  };
313};
314
315ecstatic.version = version;
316ecstatic.showDir = showDir;
317
318// Check to see if we should try to compress a file with gzip.
319function shouldCompress(req) {
320  var headers = req.headers;
321
322  return headers && headers['accept-encoding'] &&
323    headers['accept-encoding']
324      .split(",")
325      .some(function (el) {
326        return ['*','compress', 'gzip', 'deflate'].indexOf(el) != -1;
327      })
328    ;
329}
330
331// See: https://github.com/jesusabdullah/node-ecstatic/issues/109
332function decodePathname(pathname) {
333  var pieces = pathname.replace(/\\/g,"/").split('/');
334
335  return pieces.map(function (piece) {
336    piece = decodeURIComponent(piece);
337
338    if (process.platform === 'win32' && /\\/.test(piece)) {
339      throw new Error('Invalid forward slash character');
340    }
341
342    return piece;
343  }).join('/');
344}
345
346if (!module.parent) {
347  var defaults = require('./ecstatic/defaults.json')
348  var http = require('http'),
349      opts = require('minimist')(process.argv.slice(2), {
350        alias: require('./ecstatic/aliases.json'),
351        default: defaults,
352        boolean: Object.keys(defaults).filter(function (key) {
353          return typeof defaults[key] === 'boolean'
354        })
355      }),
356      envPORT = parseInt(process.env.PORT, 10),
357      port = envPORT > 1024 && envPORT <= 65536 ? envPORT : opts.port || opts.p || 8000,
358      dir = opts.root || opts._[0] || process.cwd();
359
360  if (opts.help || opts.h) {
361    var u = console.error;
362    u('usage: ecstatic [dir] {options} --port PORT');
363    u('see https://npm.im/ecstatic for more docs');
364    return;
365  }
366
367  http.createServer(ecstatic(dir, opts))
368    .listen(port, function () {
369      console.log('ecstatic serving ' + dir + ' at http://0.0.0.0:' + port);
370    });
371}