twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

index.js (4570B)


      1 /*!
      2  * serve-static
      3  * Copyright(c) 2010 Sencha Inc.
      4  * Copyright(c) 2011 TJ Holowaychuk
      5  * Copyright(c) 2014-2016 Douglas Christopher Wilson
      6  * MIT Licensed
      7  */
      8 
      9 'use strict'
     10 
     11 /**
     12  * Module dependencies.
     13  * @private
     14  */
     15 
     16 var encodeUrl = require('encodeurl')
     17 var escapeHtml = require('escape-html')
     18 var parseUrl = require('parseurl')
     19 var resolve = require('path').resolve
     20 var send = require('send')
     21 var url = require('url')
     22 
     23 /**
     24  * Module exports.
     25  * @public
     26  */
     27 
     28 module.exports = serveStatic
     29 module.exports.mime = send.mime
     30 
     31 /**
     32  * @param {string} root
     33  * @param {object} [options]
     34  * @return {function}
     35  * @public
     36  */
     37 
     38 function serveStatic (root, options) {
     39   if (!root) {
     40     throw new TypeError('root path required')
     41   }
     42 
     43   if (typeof root !== 'string') {
     44     throw new TypeError('root path must be a string')
     45   }
     46 
     47   // copy options object
     48   var opts = Object.create(options || null)
     49 
     50   // fall-though
     51   var fallthrough = opts.fallthrough !== false
     52 
     53   // default redirect
     54   var redirect = opts.redirect !== false
     55 
     56   // headers listener
     57   var setHeaders = opts.setHeaders
     58 
     59   if (setHeaders && typeof setHeaders !== 'function') {
     60     throw new TypeError('option setHeaders must be function')
     61   }
     62 
     63   // setup options for send
     64   opts.maxage = opts.maxage || opts.maxAge || 0
     65   opts.root = resolve(root)
     66 
     67   // construct directory listener
     68   var onDirectory = redirect
     69     ? createRedirectDirectoryListener()
     70     : createNotFoundDirectoryListener()
     71 
     72   return function serveStatic (req, res, next) {
     73     if (req.method !== 'GET' && req.method !== 'HEAD') {
     74       if (fallthrough) {
     75         return next()
     76       }
     77 
     78       // method not allowed
     79       res.statusCode = 405
     80       res.setHeader('Allow', 'GET, HEAD')
     81       res.setHeader('Content-Length', '0')
     82       res.end()
     83       return
     84     }
     85 
     86     var forwardError = !fallthrough
     87     var originalUrl = parseUrl.original(req)
     88     var path = parseUrl(req).pathname
     89 
     90     // make sure redirect occurs at mount
     91     if (path === '/' && originalUrl.pathname.substr(-1) !== '/') {
     92       path = ''
     93     }
     94 
     95     // create send stream
     96     var stream = send(req, path, opts)
     97 
     98     // add directory handler
     99     stream.on('directory', onDirectory)
    100 
    101     // add headers listener
    102     if (setHeaders) {
    103       stream.on('headers', setHeaders)
    104     }
    105 
    106     // add file listener for fallthrough
    107     if (fallthrough) {
    108       stream.on('file', function onFile () {
    109         // once file is determined, always forward error
    110         forwardError = true
    111       })
    112     }
    113 
    114     // forward errors
    115     stream.on('error', function error (err) {
    116       if (forwardError || !(err.statusCode < 500)) {
    117         next(err)
    118         return
    119       }
    120 
    121       next()
    122     })
    123 
    124     // pipe
    125     stream.pipe(res)
    126   }
    127 }
    128 
    129 /**
    130  * Collapse all leading slashes into a single slash
    131  * @private
    132  */
    133 function collapseLeadingSlashes (str) {
    134   for (var i = 0; i < str.length; i++) {
    135     if (str.charCodeAt(i) !== 0x2f /* / */) {
    136       break
    137     }
    138   }
    139 
    140   return i > 1
    141     ? '/' + str.substr(i)
    142     : str
    143 }
    144 
    145 /**
    146  * Create a minimal HTML document.
    147  *
    148  * @param {string} title
    149  * @param {string} body
    150  * @private
    151  */
    152 
    153 function createHtmlDocument (title, body) {
    154   return '<!DOCTYPE html>\n' +
    155     '<html lang="en">\n' +
    156     '<head>\n' +
    157     '<meta charset="utf-8">\n' +
    158     '<title>' + title + '</title>\n' +
    159     '</head>\n' +
    160     '<body>\n' +
    161     '<pre>' + body + '</pre>\n' +
    162     '</body>\n' +
    163     '</html>\n'
    164 }
    165 
    166 /**
    167  * Create a directory listener that just 404s.
    168  * @private
    169  */
    170 
    171 function createNotFoundDirectoryListener () {
    172   return function notFound () {
    173     this.error(404)
    174   }
    175 }
    176 
    177 /**
    178  * Create a directory listener that performs a redirect.
    179  * @private
    180  */
    181 
    182 function createRedirectDirectoryListener () {
    183   return function redirect (res) {
    184     if (this.hasTrailingSlash()) {
    185       this.error(404)
    186       return
    187     }
    188 
    189     // get original URL
    190     var originalUrl = parseUrl.original(this.req)
    191 
    192     // append trailing slash
    193     originalUrl.path = null
    194     originalUrl.pathname = collapseLeadingSlashes(originalUrl.pathname + '/')
    195 
    196     // reformat the URL
    197     var loc = encodeUrl(url.format(originalUrl))
    198     var doc = createHtmlDocument('Redirecting', 'Redirecting to <a href="' + escapeHtml(loc) + '">' +
    199       escapeHtml(loc) + '</a>')
    200 
    201     // send redirect response
    202     res.statusCode = 301
    203     res.setHeader('Content-Type', 'text/html; charset=UTF-8')
    204     res.setHeader('Content-Length', Buffer.byteLength(doc))
    205     res.setHeader('Content-Security-Policy', "default-src 'none'")
    206     res.setHeader('X-Content-Type-Options', 'nosniff')
    207     res.setHeader('Location', loc)
    208     res.end(doc)
    209   }
    210 }