var fs = require('fs') , util = require('util') , crypto = require('crypto') , stream = require('stream') , path = require('path') , mimetypes = require('./mimetypes') , rfc822 = require('./rfc822') ; function File (options) { stream.Stream.call(this) this.writable = true this.readable = true this.buffers = [] var self = this if (typeof options === 'string') options = {path:options} if (!options.index) options.index = 'index.html' self.writable = (typeof options.writable === "undefined") ? true : options.writable self.readable = (typeof options.readable === "undefined") ? true : options.readable self.path = options.path self.index = options.index self.on('pipe', function (src) { this.src = src }) this.buffering = true this.mimetype = options.mimetype || mimetypes.lookup(this.path.slice(this.path.lastIndexOf('.')+1)) var stopBuffering = function () { self.buffering = false while (self.buffers.length) { self.emit('data', self.buffers.shift()) } if (self.ended) self.emit('end') } fs.stat(options.path, function (err, stats) { var finish = function (err, stats) { self.stat = stats if (err && err.code === 'ENOENT' && !self.dest && !self.src) self.src = self.path if (err && !self.dest && !self.src) return self.emit('error', err) if (err && self.dest && !self.dest.writeHead) return self.emit('error', err) // See if writes are disabled if (self.src && self.src.method && !self.writable && self.dest.writeHead && (self.src.method === 'PUT' || self.src.method === 'POST')) { self.dest.writeHead(405, {'content-type':'text/plain'}) self.dest.end(self.src.method+' Not Allowed') return } if (!err) { self.etag = crypto.createHash('md5').update(stats.ino+'/'+stats.mtime+'/'+stats.size).digest("hex") self.lastmodified = rfc822.getRFC822Date(stats.mtime) } process.nextTick(function () { stopBuffering() }) // 404 and 500 if ( err && self.dest && self.dest.writeHead && // We have an error object and dest is an HTTP response ( // Either we have a source and it's a GET/HEAD or we don't have a src (self.src && (self.src.method == 'GET' || self.src.method === 'HEAD')) || (!self.src) ) ) { if (err.code === 'ENOENT') { self.dest.statusCode = 404 self.dest.end('Not Found') } else { self.dest.statusCode = 500 self.dest.end(err.message) } return } // Source is an HTTP Server Request if (self.src && (self.src.method === 'GET' || self.src.method === 'HEAD') && self.dest) { if (self.dest.setHeader) { self.dest.setHeader('content-type', self.mimetype) self.dest.setHeader('etag', self.etag) self.dest.setHeader('last-modified', self.lastmodified) } if (self.dest.writeHead) { if (self.src && self.src.headers) { if (self.src.headers['if-none-match'] === self.etag || // Lazy last-modifed matching but it's faster than parsing Datetime self.src.headers['if-modified-since'] === self.lastmodified) { self.dest.statusCode = 304 self.dest.end() return } } // We're going to return the whole file self.dest.statusCode = 200 self.dest.setHeader('content-length', stats.size) } else { // Destination is not an HTTP response, GET and HEAD method are not allowed return } if (self.src.method !== 'HEAD') { fs.createReadStream(self.path).pipe(self.dest) } return } if (self.src && (self.src.method === 'PUT' || self.src.method === 'POST')) { if (!err) { // TODO handle overwrite case return } stream.Stream.prototype.pipe.call(self, fs.createWriteStream(self.path)) if (self.dest && self.dest.writeHead) { self.on('end', function () { self.dest.statusCode = 201 self.dest.setHeader('content-length', 0) self.dest.end() }) } return } // Desination is an HTTP response, we already handled 404 and 500 if (self.dest && self.dest.writeHead) { self.dest.statusCode = 200 self.dest.setHeader('content-type', self.mimetype) self.dest.setHeader('etag', self.etag) self.dest.setHeader('last-modified', self.lastmodified) self.dest.setHeader('content-length', stats.size) fs.createReadStream(self.path).pipe(self.dest) return } // Destination is not an HTTP request if (self.src && !self.dest) { stream.Stream.prototype.pipe.call(self, fs.createWriteStream(self.path)) } else if (self.dest && !self.src) { fs.createReadStream(self.path).pipe(self.dest) } } if (!err && stats.isDirectory()) { self.path = path.join(self.path, self.index) self.mimetype = mimetypes.lookup(self.path.slice(self.path.lastIndexOf('.')+1)) fs.stat(self.path, finish) return } else { finish(err, stats) } if (!self.src && !self.dest) { if (self.buffers.length > 0) { stream.Stream.prototype.pipe.call(self, fs.createWriteStream(self.path)) } else if (self.listeners('data').length > 0) { fs.createReadStream(self.path).pipe(self.dest) } else { fs.createReadStream(self.path).pipe(self) } } }) } util.inherits(File, stream.Stream) File.prototype.pipe = function (dest, options) { this.dest = dest this.destOptions = options dest.emit('pipe', this) // stream.Stream.prototype.pipe.call(this, dest, options) } File.prototype.write = function (chunk, encoding) { if (encoding) chunk = chunk.toString(encoding) if (this.buffering) { this.buffers.push(chunk) } else { this.emit('data', chunk) } } File.prototype.end = function (chunk) { if (chunk) this.write(chunk) if (this.buffering) { this.ended = true } else { this.emit('end') } } module.exports = function (options) { return new File(options) } module.exports.File = File