Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

st

Package Overview
Dependencies
Maintainers
2
Versions
43
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

st - npm Package Compare versions

Comparing version 1.2.2 to 2.0.0

test/basic.js

139

bin/server.js
#!/usr/bin/env node
var st = require('../st.js')
var http = require('http')
var port = +(process.env.PORT || 1337)
var host = undefined
var dir = ''
var url = '/'
var cacheSize = 0
var dot = false
var index = true
var cache = true
var age = null
var cors = false
const st = require('../st.js')
const http = require('http')
let port = +(process.env.PORT || 1337)
let host
let dir = ''
let url = '/'
let dot = false
let index = true
let cache = true
let age = null
let cors = false
for (var i = 2; i < process.argv.length; i++) {
for (let i = 2; i < process.argv.length; i++) {
switch (process.argv[i]) {

@@ -48,5 +47,7 @@ case '-p':

dot = process.argv[++i]
if (dot === undefined || dot === 'true') dot = true
else if (dot === 'false') dot = false
else if (dot.charAt(0) === '-') {
if (dot === undefined || dot === 'true') {
dot = true
} else if (dot === 'false') {
dot = false
} else if (dot.charAt(0) === '-') {
--i

@@ -65,5 +66,7 @@ dot = true

index = process.argv[++i]
if (index === undefined || index === 'true') index = true
if (index === 'false') index = false
if (index.charAt(0) === '-') {
if (index === undefined || index === 'true') {
index = true
} else if (index === 'false') {
index = false
} else if (index.charAt(0) === '-') {
--i

@@ -100,3 +103,3 @@ index = true

case '--cors':
cors = true;
cors = true
break

@@ -108,43 +111,45 @@ }

console.log(
['st'
,'Static file server in node'
,''
,'Options:'
,''
,'-h --help Show this help'
,''
,'-p --port PORT Listen on PORT (default=1337)'
,''
,'-H --host HOST Bind address HOST (default=*)'
,''
,'-l --localhost Same as "--host localhost"'
,''
,'-d --dir DIRECTORY Serve the contents of DIRECTORY (default=cwd)'
,''
,'-u --url /url Serve at this mount url (default=/)'
,''
,'-i --index [INDEX] Use the specified INDEX filename as the result'
,' when a directory is requested. Set to "true"'
,' to turn autoindexing on, or "false" to turn it'
,' off. If no INDEX is provided, then it will turn'
,' autoindexing on. (default=true)'
,''
,'-ni --no-index Same as "--index false"'
,''
,'-. --dot [DOT] Allow .files to be served. Set to "false" to'
,' disable.'
,''
,'-n. --no-dot Same as "--dot false"'
,''
,'-co --cors Enable CORS to serve files to any domain.'
,''
,'-nc --no-cache Turn off all caching.'
,''
,'-a --age AGE Max age (in ms) of cache entries.'
].join('\n'))
['st',
'Static file server in node',
'',
'Options:',
'',
'-h --help Show this help',
'',
'-p --port PORT Listen on PORT (default=1337)',
'',
'-H --host HOST Bind address HOST (default=*)',
'',
'-l --localhost Same as "--host localhost"',
'',
'-d --dir DIRECTORY Serve the contents of DIRECTORY (default=cwd)',
'',
'-u --url /url Serve at this mount url (default=/)',
'',
'-i --index [INDEX] Use the specified INDEX filename as the result',
' when a directory is requested. Set to "true"',
' to turn autoindexing on, or "false" to turn it',
' off. If no INDEX is provided, then it will turn',
' autoindexing on. (default=true)',
'',
'-ni --no-index Same as "--index false"',
'',
'-. --dot [DOT] Allow .files to be served. Set to "false" to',
' disable.',
'',
'-n. --no-dot Same as "--dot false"',
'',
'-co --cors Enable CORS to serve files to any domain.',
'',
'-nc --no-cache Turn off all caching.',
'',
'-a --age AGE Max age (in ms) of cache entries.'
].join('\n'))
}
if (isNaN(port)) throw new Error('invalid port: '+port)
if (isNaN(port)) {
throw new Error('invalid port: ' + port)
}
var opt = {
const opt = {
path: dir,

@@ -168,5 +173,5 @@ url: url,

if (age) {
Object.keys(opt.cache).forEach(function (k) {
for (const k in opt.cache) {
opt.cache[k].maxAge = age
})
}
}

@@ -176,11 +181,14 @@ // maybe other cache-manipulating CLI flags?

var mount = st(opt)
const mount = st(opt)
http.createServer(function (q, s) {
if (mount(q, s)) return
if (mount(q, s)) {
return
}
s.statusCode = 404
s.end('not found')
}).listen(port, host, function() {
var addr = this.address()
var port = addr.port
}).listen(port, host, function () {
const addr = this.address()
const port = addr.port
if (!host) {

@@ -192,3 +200,4 @@ host = addr.address

}
console.log('listening at http://' + host + ':' + port)
})
{
"name": "st",
"version": "1.2.2",
"version": "2.0.0",
"description": "A module for serving static files. Does etags, caching, etc.",

@@ -8,18 +8,20 @@ "main": "st.js",

"dependencies": {
"async-cache": "~1.1.0",
"bl": "~1.2.1",
"async-cache": "^1.1.0",
"bl": "^4.0.0",
"fd": "~0.0.2",
"mime": "~1.4.1",
"negotiator": "~0.6.1"
"mime": "^2.4.4",
"negotiator": "~0.6.2"
},
"optionalDependencies": {
"graceful-fs": "~4.1.11"
"graceful-fs": "^4.2.3"
},
"devDependencies": {
"request": "~2.83.0",
"rimraf": "~2.6.2",
"tap": "~10.7.2"
"request": "^2.88.0",
"rimraf": "^3.0.0",
"standard": "^14.3.1",
"tap": "^14.9.2"
},
"scripts": {
"test": "tap test/*.js test/cli/*-test.js"
"lint": "standard",
"test": "npm run lint && tap test/*.js test/cli/*-test.js"
},

@@ -26,0 +28,0 @@ "repository": {

# st
[![Travis Status](https://api.travis-ci.org/isaacs/st.svg?branch=master)](https://travis-ci.org/isaacs/st)
A module for serving static files. Does etags, caching, etc.

@@ -12,4 +14,4 @@

```javascript
var st = require('st')
var http = require('http')
const st = require('st')
const http = require('http')

@@ -26,5 +28,7 @@ http.createServer(

```javascript
var mount = st({ path: __dirname + '/static', url: '/static' })
http.createServer(function(req, res) {
var stHandled = mount(req, res);
const path = require('path')
const mount = st({ path: path.join(__dirname, '/static'), url: '/static' })
http.createServer((req, res) => {
const stHandled = mount(req, res)
if (stHandled)

@@ -40,7 +44,7 @@ return

```javascript
var mount = st({ path: __dirname + '/static', url: '/static' })
http.createServer(function(req, res) {
mount(req, res, function() {
res.end('this is not a static file')
})
const path = require('path')
const mount = st({ path: path.join(__dirname, '/static'), url: '/static' })
http.createServer((req, res) => {
mount(req, res, () => res.end('this is not a static file'))
}).listen(1339)

@@ -54,4 +58,6 @@ ```

```javascript
var mount = st({ path: __dirname + '/static', url: '/' })
http.createServer(function(req, res) {
const path = require('path')
const mount = st({ path: path.join(__dirname, '/static'), url: '/' })
http.createServer((req, res) => {
if (shouldDoThing(req)) {

@@ -69,8 +75,8 @@ doTheThing(req, res)

```javascript
var mount = st({ path: __dirname + '/static', url: '/', passthrough: true })
http.createServer(function(req, res) {
mount(req, res, function() {
res.end('this is not a static file');
});
}).listen(1341);
const path = require('path')
const mount = st({ path: path.join(__dirname, '/static'), url: '/', passthrough: true})
http.createServer((req, res) => {
mount(req, res, () => res.end('this is not a static file'))
}).listen(1341)
```

@@ -114,4 +120,4 @@

```javascript
var st = require('st')
var mount = st({
const st = require('st')
const mount = st({
path: 'resources/static/', // resolved against the process cwd

@@ -169,3 +175,3 @@ url: 'static/', // defaults to '/'

// with bare node.js
http.createServer(function (req, res) {
http.createServer((req, res) => {
if (mount(req, res)) return // serving a static file

@@ -178,3 +184,3 @@ myCustomLogic(req, res)

// or
app.route('/static/:fooblz', function (req, res, next) {
app.route('/static/:fooblz', (req, res, next) => {
mount(req, res, next) // will call next() if it doesn't do anything

@@ -181,0 +187,0 @@ })

@@ -1,8 +0,5 @@

module.exports = st
st.Mount = Mount
var mime = require('mime')
var path = require('path')
var fs
const mime = require('mime')
const path = require('path')
const url = require('url')
let fs
try {

@@ -13,16 +10,13 @@ fs = require('graceful-fs')

}
var url = require('url')
var zlib = require('zlib')
var Neg = require('negotiator')
var http = require('http')
var AC = require('async-cache')
var util = require('util')
var FD = require('fd')
var bl = require('bl')
const zlib = require('zlib')
const Neg = require('negotiator')
const http = require('http')
const AC = require('async-cache')
const FD = require('fd')
const bl = require('bl')
// default caching options
var defaultCacheOptions = {
const defaultCacheOptions = {
fd: {
max: 1000,
maxAge: 1000 * 60 * 60,
maxAge: 1000 * 60 * 60
},

@@ -35,5 +29,3 @@ stat: {

max: 1024 * 1024 * 64,
length: function (n) {
return n.length
},
length: (n) => n.length,
maxAge: 1000 * 60 * 10

@@ -43,5 +35,3 @@ },

max: 1024 * 8,
length: function (n) {
return n.length
},
length: (n) => n.length,
maxAge: 1000 * 60 * 10

@@ -51,5 +41,3 @@ },

max: 1000,
length: function (n) {
return n.length
},
length: (n) => n.length,
maxAge: 1000 * 60 * 10

@@ -59,4 +47,20 @@ }

// lru-cache doesn't like when max=0, so we just pretend
// everything is really big. kind of a kludge, but easiest way
// to get it done
const none = {
max: 1,
length: () => Infinity
}
const noCaching = {
fd: none,
stat: none,
index: none,
readdir: none,
content: none
}
function st (opt) {
var p, u
let p, u
if (typeof opt === 'string') {

@@ -71,11 +75,24 @@ p = opt

if (!opt) opt = {}
else opt = util._extend({}, opt)
if (!opt) {
opt = {}
} else {
opt = Object.assign({}, opt)
}
if (!p) p = opt.path
if (typeof p !== 'string') throw new Error('no path specified')
if (!p) {
p = opt.path
}
if (typeof p !== 'string') {
throw new Error('no path specified')
}
p = path.resolve(p)
if (!u) u = opt.url
if (!u) u = ''
if (u.charAt(0) !== '/') u = '/' + u
if (!u) {
u = opt.url
}
if (!u) {
u = ''
}
if (u.charAt(0) !== '/') {
u = '/' + u
}

@@ -85,4 +102,4 @@ opt.url = u

var m = new Mount(opt)
var fn = m.serve.bind(m)
const m = new Mount(opt)
const fn = m.serve.bind(m)
fn._this = m

@@ -92,400 +109,418 @@ return fn

function Mount (opt) {
if (!opt) throw new Error('no options provided')
if (typeof opt !== 'object') throw new Error('invalid options')
if (!(this instanceof Mount)) return new Mount(opt)
class Mount {
constructor (opt) {
if (!opt) {
throw new Error('no options provided')
}
if (typeof opt !== 'object') {
throw new Error('invalid options')
}
if (!(this instanceof Mount)) {
return new Mount(opt)
}
this.opt = opt
this.url = opt.url
this.path = opt.path
this._index = opt.index === false ? false
: typeof opt.index === 'string' ? opt.index
: true
this.fdman = FD()
this.opt = opt
this.url = opt.url
this.path = opt.path
this._index = opt.index === false ? false
: typeof opt.index === 'string' ? opt.index
: true
this.fdman = FD()
// cache basically everything
var c = this.getCacheOptions(opt)
this.cache = {
fd: AC(c.fd),
stat: AC(c.stat),
index: AC(c.index),
readdir: AC(c.readdir),
content: AC(c.content)
// cache basically everything
const c = this.getCacheOptions(opt)
this.cache = {
fd: AC(c.fd),
stat: AC(c.stat),
index: AC(c.index),
readdir: AC(c.readdir),
content: AC(c.content)
}
this._cacheControl =
c.content.maxAge === false
? undefined
: typeof c.content.cacheControl === 'string'
? c.content.cacheControl
: opt.cache === false
? 'no-cache'
: 'public, max-age=' + (c.content.maxAge / 1000)
}
this._cacheControl =
c.content.maxAge === false
? undefined
: typeof c.content.cacheControl == 'string'
? c.content.cacheControl
: opt.cache === false
? 'no-cache'
: 'public, max-age=' + (c.content.maxAge / 1000)
}
getCacheOptions (opt) {
let o = opt.cache
const set = (key) => {
return o[key] === false
? Object.assign({}, none)
: Object.assign(Object.assign({}, d[key]), o[key])
}
// lru-cache doesn't like when max=0, so we just pretend
// everything is really big. kind of a kludge, but easiest way
// to get it done
var none = { max: 1, length: function() {
return Infinity
}}
var noCaching = {
fd: none,
stat: none,
index: none,
readdir: none,
content: none
}
if (o === false) {
o = noCaching
} else if (!o) {
o = {}
}
Mount.prototype.getCacheOptions = function (opt) {
var o = opt.cache
, set = function (key) {
return o[key] === false
? util._extend({}, none)
: util._extend(util._extend({}, d[key]), o[key])
}
const d = defaultCacheOptions
if (o === false)
o = noCaching
else if (!o)
o = {}
// should really only ever set max and maxAge here.
// load and fd disposal is important to control.
const c = {
fd: set('fd'),
stat: set('stat'),
index: set('index'),
readdir: set('readdir'),
content: set('content')
}
var d = defaultCacheOptions
c.fd.dispose = this.fdman.close.bind(this.fdman)
c.fd.load = this.fdman.open.bind(this.fdman)
// should really only ever set max and maxAge here.
// load and fd disposal is important to control.
var c = {
fd: set('fd'),
stat: set('stat'),
index: set('index'),
readdir: set('readdir'),
content: set('content'),
c.stat.load = this._loadStat.bind(this)
c.index.load = this._loadIndex.bind(this)
c.readdir.load = this._loadReaddir.bind(this)
c.content.load = this._loadContent.bind(this)
return c
}
c.fd.dispose = this.fdman.close.bind(this.fdman)
c.fd.load = this.fdman.open.bind(this.fdman)
// get the path component from a URI
getUriPath (u) {
let p = url.parse(u).pathname // eslint-disable-line
c.stat.load = this._loadStat.bind(this)
c.index.load = this._loadIndex.bind(this)
c.readdir.load = this._loadReaddir.bind(this)
c.content.load = this._loadContent.bind(this)
return c
}
// Encoded dots are dots
p = p.replace(/%2e/ig, '.')
// get the path component from a URI
Mount.prototype.getUriPath = function (u) {
var p = url.parse(u).pathname
// encoded slashes are /
p = p.replace(/%2f|%5c/ig, '/')
// Encoded dots are dots
p = p.replace(/%2e/ig, '.')
// back slashes are slashes
p = p.replace(/[/\\]/g, '/')
// encoded slashes are /
p = p.replace(/%2f|%5c/ig, '/')
// Make sure it starts with a slash
p = p.replace(/^\//, '/')
if ((/[/\\]\.\.([/\\]|$)/).test(p)) {
// traversal urls not ever even slightly allowed. clearly shenanigans
// send a 403 on that noise, do not pass go, do not collect $200
return 403
}
// back slashes are slashes
p = p.replace(/[\/\\]/g, '/')
u = path.normalize(p).replace(/\\/g, '/')
if (u.indexOf(this.url) !== 0) {
return false
}
// Make sure it starts with a slash
p = p.replace(/^\//, '/')
if ((/[\/\\]\.\.([\/\\]|$)/).test(p)) {
// traversal urls not ever even slightly allowed. clearly shenanigans
// send a 403 on that noise, do not pass go, do not collect $200
return 403
}
try {
u = decodeURIComponent(u)
} catch (e) {
// if decodeURIComponent failed, we weren't given a valid URL to begin with.
return false
}
u = path.normalize(p).replace(/\\/g, '/')
if (u.indexOf(this.url) !== 0) return false
// /a/b/c mounted on /path/to/z/d/x
// /a/b/c/d --> /path/to/z/d/x/d
u = u.substr(this.url.length)
if (u.charAt(0) !== '/') {
u = '/' + u
}
try {
u = decodeURIComponent(u)
return u
}
catch (e) {
// if decodeURIComponent failed, we weren't given a valid URL to begin with.
return false
}
// /a/b/c mounted on /path/to/z/d/x
// /a/b/c/d --> /path/to/z/d/x/d
u = u.substr(this.url.length)
if (u.charAt(0) !== '/') u = '/' + u
return u
}
// get a path from a url
Mount.prototype.getPath = function (u) {
return path.join(this.path, u)
}
// get a url from a path
Mount.prototype.getUrl = function (p) {
p = path.resolve(p)
if (p.indexOf(this.path) !== 0) return false
p = path.join('/', p.substr(this.path.length))
var u = path.join(this.url, p).replace(/\\/g, '/')
return u
}
Mount.prototype.serve = function (req, res, next) {
if (req.method !== 'HEAD' && req.method !== 'GET') {
if (typeof next === 'function') next()
return false
// get a path from a url
getPath (u) {
return path.join(this.path, u)
}
// querystrings are of no concern to us
if (!req.sturl)
req.sturl = this.getUriPath(req.url)
// don't allow dot-urls by default, unless explicitly allowed.
// If we got a 403, then it's explicitly forbidden.
if (req.sturl === 403 || (!this.opt.dot && (/(^|\/)\./).test(req.sturl))) {
res.statusCode = 403
res.end('Forbidden')
return true
// get a url from a path
getUrl (p) {
p = path.resolve(p)
if (p.indexOf(this.path) !== 0) {
return false
}
p = path.join('/', p.substr(this.path.length))
const u = path.join(this.url, p).replace(/\\/g, '/')
return u
}
// Falsey here means we got some kind of invalid path.
// Probably urlencoding we couldn't understand, or some
// other "not compatible with st, but maybe ok" thing.
if (typeof req.sturl !== 'string' || req.sturl == '') {
if (typeof next === 'function') next()
return false
}
serve (req, res, next) {
if (req.method !== 'HEAD' && req.method !== 'GET') {
if (typeof next === 'function') {
next()
}
return false
}
var p = this.getPath(req.sturl)
// querystrings are of no concern to us
if (!req.sturl) {
req.sturl = this.getUriPath(req.url)
}
// now we have a path. check for the fd.
this.cache.fd.get(p, function (er, fd) {
// inability to open is some kind of error, probably 404
// if we're in passthrough, AND got a next function, we can
// fall through to that. otherwise, we already returned true,
// send an error.
if (er) {
if (this.opt.passthrough === true && er.code === 'ENOENT' && next)
return next()
return this.error(er, res)
// don't allow dot-urls by default, unless explicitly allowed.
// If we got a 403, then it's explicitly forbidden.
if (req.sturl === 403 || (!this.opt.dot && (/(^|\/)\./).test(req.sturl))) {
res.statusCode = 403
res.end('Forbidden')
return true
}
// we may be about to use this, so don't let it be closed by cache purge
this.fdman.checkout(p, fd)
// a safe end() function that can be called multiple times but
// only perform a single checkin
var end = this.fdman.checkinfn(p, fd)
// Falsey here means we got some kind of invalid path.
// Probably urlencoding we couldn't understand, or some
// other "not compatible with st, but maybe ok" thing.
if (typeof req.sturl !== 'string' || req.sturl === '') {
if (typeof next === 'function') {
next()
}
return false
}
this.cache.stat.get(fd+':'+p, function (er, stat) {
const p = this.getPath(req.sturl)
// now we have a path. check for the fd.
this.cache.fd.get(p, (er, fd) => {
// inability to open is some kind of error, probably 404
// if we're in passthrough, AND got a next function, we can
// fall through to that. otherwise, we already returned true,
// send an error.
if (er) {
if (next && this.opt.passthrough === true && this._index === false) {
if (this.opt.passthrough === true && er.code === 'ENOENT' && next) {
return next()
}
end()
return this.error(er, res)
}
var isDirectory = stat.isDirectory()
// we may be about to use this, so don't let it be closed by cache purge
this.fdman.checkout(p, fd)
// a safe end() function that can be called multiple times but
// only perform a single checkin
const end = this.fdman.checkinfn(p, fd)
if (isDirectory) {
end() // we won't need this fd for a directory in any case
if (next && this.opt.passthrough === true && this._index === false) {
// this is done before if-modified-since and if-non-match checks so
// cached modified and etag values won't return 304's if we've since
// switched to !index. See Issue #51.
return next()
this.cache.stat.get(fd + ':' + p, (er, stat) => {
if (er) {
if (next && this.opt.passthrough === true && this._index === false) {
return next()
}
end()
return this.error(er, res)
}
}
var ims = req.headers['if-modified-since']
if (ims) ims = new Date(ims).getTime()
if (ims && ims >= stat.mtime.getTime()) {
res.statusCode = 304
res.end()
return end()
}
const isDirectory = stat.isDirectory()
var etag = getEtag(stat)
if (req.headers['if-none-match'] === etag) {
res.statusCode = 304
res.end()
return end()
}
if (isDirectory) {
end() // we won't need this fd for a directory in any case
if (next && this.opt.passthrough === true && this._index === false) {
// this is done before if-modified-since and if-non-match checks so
// cached modified and etag values won't return 304's if we've since
// switched to !index. See Issue #51.
return next()
}
}
// only set headers once we're sure we'll be serving this request
if (!res.getHeader('cache-control') && this._cacheControl)
res.setHeader('cache-control', this._cacheControl)
res.setHeader('last-modified', stat.mtime.toUTCString())
res.setHeader('etag', etag)
let ims = req.headers['if-modified-since']
if (ims) {
ims = new Date(ims).getTime()
}
if (ims && ims >= stat.mtime.getTime()) {
res.statusCode = 304
res.end()
return end()
}
if (this.opt.cors) {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, Range')
}
const etag = getEtag(stat)
if (req.headers['if-none-match'] === etag) {
res.statusCode = 304
res.end()
return end()
}
return isDirectory
? this.index(p, req, res)
: this.file(p, fd, stat, etag, req, res, end)
}.bind(this))
}.bind(this))
// only set headers once we're sure we'll be serving this request
if (!res.getHeader('cache-control') && this._cacheControl) {
res.setHeader('cache-control', this._cacheControl)
}
res.setHeader('last-modified', stat.mtime.toUTCString())
res.setHeader('etag', etag)
return true
}
if (this.opt.cors) {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, Range')
}
Mount.prototype.error = function (er, res) {
res.statusCode = typeof er === 'number' ? er
: er.code === 'ENOENT' || er.code === 'EISDIR' ? 404
: er.code === 'EPERM' || er.code === 'EACCES' ? 403
: 500
return isDirectory
? this.index(p, req, res)
: this.file(p, fd, stat, etag, req, res, end)
})
})
if (typeof res.error === 'function') {
// pattern of express and ErrorPage
return res.error(res.statusCode, er)
return true
}
res.setHeader('content-type', 'text/plain')
res.end(http.STATUS_CODES[res.statusCode] + '\n')
}
error (er, res) {
res.statusCode = typeof er === 'number' ? er
: er.code === 'ENOENT' || er.code === 'EISDIR' ? 404
: er.code === 'EPERM' || er.code === 'EACCES' ? 403
: 500
Mount.prototype.index = function (p, req, res) {
if (this._index === true) {
return this.autoindex(p, req, res)
if (typeof res.error === 'function') {
// pattern of express and ErrorPage
return res.error(res.statusCode, er)
}
res.setHeader('content-type', 'text/plain')
res.end(http.STATUS_CODES[res.statusCode] + '\n')
}
if (typeof this._index === 'string') {
if (!/\/$/.test(req.sturl)) req.sturl += '/'
req.sturl += this._index
return this.serve(req, res)
}
return this.error(404, res)
}
Mount.prototype.autoindex = function (p, req, res) {
if (!/\/$/.exec(req.sturl)) {
res.statusCode = 301
res.setHeader('location', req.sturl + '/')
res.end('Moved: ' + req.sturl + '/')
return
index (p, req, res) {
if (this._index === true) {
return this.autoindex(p, req, res)
}
if (typeof this._index === 'string') {
if (!/\/$/.test(req.sturl)) {
req.sturl += '/'
}
req.sturl += this._index
return this.serve(req, res)
}
return this.error(404, res)
}
this.cache.index.get(p, function (er, html) {
if (er) return this.error(er, res)
autoindex (p, req, res) {
if (!/\/$/.exec(req.sturl)) {
res.statusCode = 301
res.setHeader('location', req.sturl + '/')
res.end('Moved: ' + req.sturl + '/')
return
}
res.statusCode = 200
res.setHeader('content-type', 'text/html')
res.setHeader('content-length', html.length)
res.end(html)
}.bind(this))
}
this.cache.index.get(p, (er, html) => {
if (er) {
return this.error(er, res)
}
Mount.prototype.file = function (p, fd, stat, etag, req, res, end) {
var key = stat.size + ':' + etag
var mt = mime.lookup(path.extname(p))
if (mt !== 'application/octet-stream') {
res.setHeader('content-type', mt)
res.statusCode = 200
res.setHeader('content-type', 'text/html')
res.setHeader('content-length', html.length)
res.end(html)
})
}
// only use the content cache if it will actually fit there.
if (this.cache.content.has(key)) {
end()
this.cachedFile(p, stat, etag, req, res)
} else {
this.streamFile(p, fd, stat, etag, req, res, end)
}
}
file (p, fd, stat, etag, req, res, end) {
const key = stat.size + ':' + etag
Mount.prototype.cachedFile = function (p, stat, etag, req, res) {
var key = stat.size + ':' + etag
var gz = this.opt.gzip !== false && getGz(p, req)
const mt = mime.getType(path.extname(p))
if (mt !== 'application/octet-stream') {
res.setHeader('content-type', mt)
}
this.cache.content.get(key, function (er, content) {
if (er) return this.error(er, res)
res.statusCode = 200
if (this.opt.cachedHeader)
res.setHeader('x-from-cache', 'true')
if (gz && content.gz) {
res.setHeader('content-encoding', 'gzip')
res.setHeader('content-length', content.gz.length)
res.end(content.gz)
// only use the content cache if it will actually fit there.
if (this.cache.content.has(key)) {
end()
this.cachedFile(p, stat, etag, req, res)
} else {
res.setHeader('content-length', content.length)
res.end(content)
this.streamFile(p, fd, stat, etag, req, res, end)
}
}.bind(this))
}
}
Mount.prototype.streamFile = function (p, fd, stat, etag, req, res, end) {
var streamOpt = { fd: fd, start: 0, end: stat.size }
var stream = fs.createReadStream(p, streamOpt)
stream.destroy = function () {}
cachedFile (p, stat, etag, req, res) {
const key = stat.size + ':' + etag
const gz = this.opt.gzip !== false && getGz(p, req)
// gzip only if not explicitly turned off or client doesn't accept it
var gzOpt = this.opt.gzip !== false
var gz = gzOpt && getGz(p, req)
var cachable = this.cache.content._cache.max > stat.size
var gzstr
this.cache.content.get(key, (er, content) => {
if (er) {
return this.error(er, res)
}
res.statusCode = 200
if (this.opt.cachedHeader) {
res.setHeader('x-from-cache', 'true')
}
if (gz && content.gz) {
res.setHeader('content-encoding', 'gzip')
res.setHeader('content-length', content.gz.length)
res.end(content.gz)
} else {
res.setHeader('content-length', content.length)
res.end(content)
}
})
}
// need a gzipped version for the cache, so do it regardless of what the client wants
if (gz || (gzOpt && cachable)) gzstr = zlib.Gzip()
streamFile (p, fd, stat, etag, req, res, end) {
const streamOpt = { fd: fd, start: 0, end: stat.size }
let stream = fs.createReadStream(p, streamOpt)
stream.destroy = () => {}
// too late to effectively handle any errors.
// just kill the connection if that happens.
stream.on('error', function(e) {
console.error('Error serving %s fd=%d\n%s', p, fd, e.stack || e.message)
res.socket.destroy()
end()
})
// gzip only if not explicitly turned off or client doesn't accept it
const gzOpt = this.opt.gzip !== false
const gz = gzOpt && getGz(p, req)
const cachable = this.cache.content._cache.max > stat.size
let gzstr
if (res.filter) stream = stream.pipe(res.filter)
// need a gzipped version for the cache, so do it regardless of what the client wants
if (gz || (gzOpt && cachable)) {
gzstr = zlib.Gzip()
}
res.statusCode = 200
// too late to effectively handle any errors.
// just kill the connection if that happens.
stream.on('error', (e) => {
console.error('Error serving %s fd=%d\n%s', p, fd, e.stack || e.message)
res.socket.destroy()
end()
})
if (gz) {
// we don't know how long it'll be, since it will be compressed.
res.setHeader('content-encoding', 'gzip')
stream.pipe(gzstr).pipe(res)
} else {
if (!res.filter) res.setHeader('content-length', stat.size)
stream.pipe(res)
if (gzstr)
stream.pipe(gzstr) // for cache
}
if (res.filter) {
stream = stream.pipe(res.filter)
}
stream.on('end', function () {
process.nextTick(end)
})
res.statusCode = 200
if (cachable) {
// collect it, and put it in the cache
if (gz) {
// we don't know how long it'll be, since it will be compressed.
res.setHeader('content-encoding', 'gzip')
stream.pipe(gzstr).pipe(res)
} else {
if (!res.filter) {
res.setHeader('content-length', stat.size)
}
stream.pipe(res)
if (gzstr) {
stream.pipe(gzstr)
} // for cache
}
var calls = 0
stream.on('end', () => process.nextTick(end))
// called by bl() for both the raw stream and gzipped stream if we're
// caching gzipped data
var collectEnd = function () {
if (++calls == (gzOpt ? 2 : 1)) {
var content = bufs.slice()
content.gz = gzbufs && gzbufs.slice()
this.cache.content.set(key, content)
if (cachable) {
// collect it, and put it in the cache
let calls = 0
// called by bl() for both the raw stream and gzipped stream if we're
// caching gzipped data
const collectEnd = () => {
if (++calls === (gzOpt ? 2 : 1)) {
const content = bufs.slice()
content.gz = gzbufs && gzbufs.slice()
this.cache.content.set(key, content)
}
}
}.bind(this)
var key = stat.size + ':' + etag
var bufs = bl(collectEnd)
var gzbufs
const key = stat.size + ':' + etag
const bufs = bl(collectEnd)
let gzbufs
stream.pipe(bufs)
stream.pipe(bufs)
if (gzstr) {
gzbufs = bl(collectEnd)
gzstr.pipe(gzbufs)
if (gzstr) {
gzbufs = bl(collectEnd)
gzstr.pipe(gzbufs)
}
}
}
}
// cache-fillers
// cache-fillers
Mount.prototype._loadIndex = function (p, cb) {
// truncate off the first bits
var url = p.substr(this.path.length).replace(/\\/g, '/')
var t = url
_loadIndex (p, cb) {
// truncate off the first bits
const url = p.substr(this.path.length).replace(/\\/g, '/')
const t = url
.replace(/"/g, '&quot;')

@@ -496,20 +531,22 @@ .replace(/</g, '&lt;')

var str =
'<!doctype html>' +
'<html>' +
'<head><title>Index of ' + t + '</title></head>' +
'<body>' +
'<h1>Index of ' + t + '</h1>' +
'<hr><pre><a href="../">../</a>\n'
let str =
'<!doctype html>' +
'<html>' +
'<head><title>Index of ' + t + '</title></head>' +
'<body>' +
'<h1>Index of ' + t + '</h1>' +
'<hr><pre><a href="../">../</a>\n'
this.cache.readdir.get(p, function (er, data) {
if (er) return cb(er)
this.cache.readdir.get(p, (er, data) => {
if (er) {
return cb(er)
}
var nameLen = 0
var sizeLen = 0
let nameLen = 0
let sizeLen = 0
Object.keys(data).map(function (f) {
var d = data[f]
Object.keys(data).map((f) => {
const d = data[f]
var name = f
let name = f
.replace(/"/g, '&quot;')

@@ -520,82 +557,98 @@ .replace(/</g, '&lt;')

if (d.size === '-') name += '/'
var showName = name.replace(/^(.{40}).{3,}$/, '$1..>')
var linkName = encodeURIComponent(name)
.replace(/%2e/ig, '.') // Encoded dots are dots
.replace(/%2f|%5c/ig, '/') // encoded slashes are /
.replace(/[\/\\]/g, '/') // back slashes are slashes
if (d.size === '-') {
name += '/'
}
const showName = name.replace(/^(.{40}).{3,}$/, '$1..>')
const linkName = encodeURIComponent(name)
.replace(/%2e/ig, '.') // Encoded dots are dots
.replace(/%2f|%5c/ig, '/') // encoded slashes are /
.replace(/[/\\]/g, '/') // back slashes are slashes
nameLen = Math.max(nameLen, showName.length)
sizeLen = Math.max(sizeLen, ('' + d.size).length)
return [ '<a href="' + linkName + '">' + showName + '</a>',
d.mtime, d.size, showName ]
}).sort(function (a, b) {
return a[2] === '-' && b[2] !== '-' ? -1 // dirs first
: a[2] !== '-' && b[2] === '-' ? 1
: a[0].toLowerCase() < b[0].toLowerCase() ? -1 // then alpha
: a[0].toLowerCase() > b[0].toLowerCase() ? 1
: 0
}).forEach(function (line) {
var namePad = new Array(8 + nameLen - line[3].length).join(' ')
var sizePad = new Array(8 + sizeLen - ('' + line[2]).length).join(' ')
str += line[0] + namePad +
line[1].toISOString() +
sizePad + line[2] + '\n'
nameLen = Math.max(nameLen, showName.length)
sizeLen = Math.max(sizeLen, ('' + d.size).length)
return ['<a href="' + linkName + '">' + showName + '</a>',
d.mtime, d.size, showName]
}).sort((a, b) => {
return a[2] === '-' && b[2] !== '-' ? -1 // dirs first
: a[2] !== '-' && b[2] === '-' ? 1
: a[0].toLowerCase() < b[0].toLowerCase() ? -1 // then alpha
: a[0].toLowerCase() > b[0].toLowerCase() ? 1
: 0
}).forEach((line) => {
const namePad = new Array(8 + nameLen - line[3].length).join(' ')
const sizePad = new Array(8 + sizeLen - ('' + line[2]).length).join(' ')
str += line[0] + namePad +
line[1].toISOString() +
sizePad + line[2] + '\n'
})
str += '</pre><hr></body></html>'
cb(null, Buffer.from(str))
})
}
str += '</pre><hr></body></html>'
cb(null, new Buffer(str))
})
}
_loadReaddir (p, cb) {
let len
let data
fs.readdir(p, (er, files) => {
if (er) {
return cb(er)
}
files = files.filter((f) => {
if (!this.opt.dot) {
return !/^\./.test(f)
} else {
return f !== '.' && f !== '..'
}
})
len = files.length
data = {}
files.forEach((file) => {
const pf = path.join(p, file)
this.cache.stat.get(pf, (er, stat) => {
if (er) {
return cb(er)
}
if (stat.isDirectory()) {
stat.size = '-'
}
data[file] = stat
next()
})
})
})
Mount.prototype._loadReaddir = function (p, cb) {
var len
var data
fs.readdir(p, function (er, files) {
if (er) return cb(er)
files = files.filter(function (f) {
if (!this.opt.dot) return !/^\./.test(f)
else return f !== '.' && f !== '..'
}.bind(this))
len = files.length
data = {}
files.forEach(function (file) {
var pf = path.join(p, file)
this.cache.stat.get(pf, function (er, stat) {
if (er) return cb(er)
if (stat.isDirectory()) stat.size = '-'
data[file] = stat
next()
}.bind(this))
}.bind(this))
}.bind(this))
const next = () => {
if (--len === 0) {
cb(null, data)
}
}
}
function next () {
if (--len === 0) cb(null, data)
_loadStat (key, cb) {
// key is either fd:path or just a path
const fdp = key.match(/^(\d+):(.*)/)
if (fdp) {
const fd = +fdp[1]
const p = fdp[2]
fs.fstat(fd, (er, stat) => {
if (er) {
return cb(er)
}
this.cache.stat.set(p, stat)
cb(null, stat)
})
} else {
fs.stat(key, cb)
}
}
}
Mount.prototype._loadStat = function (key, cb) {
// key is either fd:path or just a path
var fdp = key.match(/^(\d+):(.*)/)
if (fdp) {
var fd = +fdp[1]
var p = fdp[2]
fs.fstat(fd, function (er, stat) {
if (er) return cb(er)
this.cache.stat.set(p, stat)
cb(null, stat)
}.bind(this))
} else {
fs.stat(key, cb)
_loadContent () {
// this function should never be called.
// we check if the thing is in the cache, and if not, stream it in
// manually. this.cache.content.get() should not ever happen.
throw new Error('This should not ever happen')
}
}
Mount.prototype._loadContent = function () {
// this function should never be called.
// we check if the thing is in the cache, and if not, stream it in
// manually. this.cache.content.get() should not ever happen.
throw new Error('This should not ever happen')
}
function getEtag (s) {

@@ -605,6 +658,6 @@ return '"' + s.dev + '-' + s.ino + '-' + s.mtime.getTime() + '"'

function getGz (p,req) {
var gz = false
function getGz (p, req) {
let gz = false
if (!/\.t?gz$/.exec(p)) {
var neg = req.negotiator || new Neg(req)
const neg = req.negotiator || new Neg(req)
gz = neg.preferredEncoding(['gzip', 'identity']) === 'gzip'

@@ -614,1 +667,4 @@ }

}
module.exports = st
module.exports.Mount = Mount

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc