pax_global_header00006660000000000000000000000064124106130720014506gustar00rootroot0000000000000052 comment=e7e1c0a0514ce75f132242a0aeba435c848f4ab1 serve-favicon-2.1.5/000077500000000000000000000000001241061307200142625ustar00rootroot00000000000000serve-favicon-2.1.5/.gitignore000066400000000000000000000000461241061307200162520ustar00rootroot00000000000000coverage/ node_modules/ npm-debug.log serve-favicon-2.1.5/.travis.yml000066400000000000000000000003711241061307200163740ustar00rootroot00000000000000language: node_js node_js: - "0.8" - "0.10" - "0.11" matrix: allow_failures: - node_js: "0.11" fast_finish: true script: "npm run-script test-travis" after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls" serve-favicon-2.1.5/HISTORY.md000066400000000000000000000022731241061307200157510ustar00rootroot000000000000002.1.5 / 2014-09-24 ================== * deps: etag@~1.4.0 2.1.4 / 2014-09-15 ================== * Fix content headers being sent in 304 response * deps: etag@~1.3.1 - Improve ETag generation speed 2.1.3 / 2014-09-07 ================== * deps: fresh@0.2.4 2.1.2 / 2014-09-05 ================== * deps: etag@~1.3.0 - Improve ETag generation speed 2.1.1 / 2014-08-25 ================== * Fix `ms` to be listed as a dependency 2.1.0 / 2014-08-24 ================== * Accept string for `maxAge` (converted by `ms`) * Use `etag` to generate `ETag` header 2.0.1 / 2014-06-05 ================== * Reduce byte size of `ETag` header 2.0.0 / 2014-05-02 ================== * `path` argument is required; there is no default icon. * Accept `Buffer` of icon as first argument. * Non-GET and HEAD requests are denied. * Send valid max-age value * Support conditional requests * Support max-age=0 * Support OPTIONS method * Throw if `path` argument is directory. 1.0.2 / 2014-03-16 ================== * Fixed content of default icon. 1.0.1 / 2014-03-11 ================== * Fixed path to default icon. 1.0.0 / 2014-02-15 ================== * Initial release serve-favicon-2.1.5/LICENSE000066400000000000000000000022371241061307200152730ustar00rootroot00000000000000(The MIT License) Copyright (c) 2010 Sencha Inc. Copyright (c) 2011 LearnBoost Copyright (c) 2011 TJ Holowaychuk Copyright (c) 2014 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.serve-favicon-2.1.5/README.md000066400000000000000000000053201241061307200155410ustar00rootroot00000000000000# serve-favicon [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![Build Status][travis-image]][travis-url] [![Test Coverage][coveralls-image]][coveralls-url] [![Gittip][gittip-image]][gittip-url] Node.js middleware for serving a favicon. ## Install ```bash npm install serve-favicon ``` ## API ### favicon(path, options) Create new middleware to serve a favicon from the given `path` to a favicon file. `path` may also be a `Buffer` of the icon to serve. #### Options Serve favicon accepts these properties in the options object. ##### maxAge The `cache-control` `max-age` directive in `ms`, defaulting to 1 day. This can also be a string accepted by the [ms](https://www.npmjs.org/package/ms#readme) module. ## Examples Typically this middleware will come very early in your stack (maybe even first) to avoid processing any other middleware if we already know the request is for `/favicon.ico`. ### express ```javascript var express = require('express'); var favicon = require('serve-favicon'); var app = express(); app.use(favicon(__dirname + '/public/favicon.ico')); // Add your routes here, etc. app.listen(3000); ``` ### connect ```javascript var connect = require('connect'); var favicon = require('serve-favicon'); var app = connect(); app.use(favicon(__dirname + '/public/favicon.ico')); // Add your middleware here, etc. app.listen(3000); ``` ### vanilla http server This middleware can be used anywhere, even outside express/connect. It takes `req`, `res`, and `callback`. ```javascript var http = require('http'); var favicon = require('serve-favicon'); var finalhandler = require('finalhandler'); var _favicon = favicon(__dirname + '/public/favicon.ico'); var server = http.createServer(function onRequest(req, res) { var done = finalhandler(req, res); _favicon(req, res, function onNext(err) { if (err) return done(err); // continue to process the request here, etc. res.statusCode = 404; res.end('oops'); }); }); server.listen(3000); ``` ## License [MIT](LICENSE) [npm-image]: https://img.shields.io/npm/v/serve-favicon.svg?style=flat [npm-url]: https://npmjs.org/package/serve-favicon [travis-image]: https://img.shields.io/travis/expressjs/serve-favicon.svg?style=flat [travis-url]: https://travis-ci.org/expressjs/serve-favicon [coveralls-image]: https://img.shields.io/coveralls/expressjs/serve-favicon.svg?style=flat [coveralls-url]: https://coveralls.io/r/expressjs/serve-favicon?branch=master [downloads-image]: https://img.shields.io/npm/dm/serve-favicon.svg?style=flat [downloads-url]: https://npmjs.org/package/serve-favicon [gittip-image]: https://img.shields.io/gittip/dougwilson.svg?style=flat [gittip-url]: https://www.gittip.com/dougwilson/ serve-favicon-2.1.5/index.js000066400000000000000000000052401241061307200157300ustar00rootroot00000000000000/*! * serve-favicon * Copyright(c) 2010 Sencha Inc. * Copyright(c) 2011 TJ Holowaychuk * Copyright(c) 2014 Douglas Christopher Wilson * MIT Licensed */ /** * Module dependencies. */ var etag = require('etag'); var fresh = require('fresh'); var fs = require('fs'); var ms = require('ms'); var path = require('path'); var resolve = path.resolve; /** * Module variables. */ var maxMaxAge = 60 * 60 * 24 * 365 * 1000; // 1 year /** * Serves the favicon located by the given `path`. * * @param {String|Buffer} path * @param {Object} options * @return {Function} middleware * @api public */ module.exports = function favicon(path, options){ options = options || {}; var buf; var icon; // favicon cache var maxAge = calcMaxAge(options.maxAge); var stat; if (!path) throw new TypeError('path to favicon.ico is required'); if (Buffer.isBuffer(path)) { buf = new Buffer(path.length); path.copy(buf); icon = createIcon(buf, maxAge); } else if (typeof path === 'string') { path = resolve(path); stat = fs.statSync(path); if (stat.isDirectory()) throw createIsDirError(path); } else { throw new TypeError('path to favicon.ico must be string or buffer'); } return function favicon(req, res, next){ if ('/favicon.ico' !== req.url) return next(); if ('GET' !== req.method && 'HEAD' !== req.method) { var status = 'OPTIONS' === req.method ? 200 : 405; res.writeHead(status, {'Allow': 'GET, HEAD, OPTIONS'}); res.end(); return; } if (icon) return send(req, res, icon); fs.readFile(path, function(err, buf){ if (err) return next(err); icon = createIcon(buf, maxAge); send(req, res, icon); }); }; }; function calcMaxAge(val) { var num = typeof val === 'string' ? ms(val) : val; return num != null ? Math.min(Math.max(0, num), maxMaxAge) : maxMaxAge } function createIcon(buf, maxAge) { return { body: buf, headers: { 'Cache-Control': 'public, max-age=' + ~~(maxAge / 1000), 'ETag': etag(buf) } }; } function createIsDirError(path) { var error = new Error('EISDIR, illegal operation on directory \'' + path + '\''); error.code = 'EISDIR'; error.errno = 28; error.path = path; error.syscall = 'open'; return error; } function send(req, res, icon) { var headers = icon.headers; // Set headers for (var header in headers) { res.setHeader(header, headers[header]); } if (fresh(req.headers, res._headers)) { res.statusCode = 304; res.end(); return; } res.statusCode = 200; res.setHeader('Content-Length', icon.body.length); res.setHeader('Content-Type', 'image/x-icon'); res.end(icon.body); } serve-favicon-2.1.5/package.json000066400000000000000000000017131241061307200165520ustar00rootroot00000000000000{ "name": "serve-favicon", "description": "favicon serving middleware with caching", "version": "2.1.5", "author": "Douglas Christopher Wilson ", "license": "MIT", "keywords": [ "express", "favicon", "middleware" ], "repository": "expressjs/serve-favicon", "dependencies": { "etag": "~1.4.0", "fresh": "0.2.4", "ms": "0.6.2" }, "devDependencies": { "istanbul": "0.3.2", "mocha": "~1.21.4", "proxyquire": "~1.0.1", "should": "~4.0.1", "supertest": "~0.13.0" }, "files": [ "LICENSE", "HISTORY.md", "index.js" ], "engines": { "node": ">= 0.8.0" }, "scripts": { "test": "mocha --reporter spec --bail --check-leaks test/", "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" } } serve-favicon-2.1.5/test/000077500000000000000000000000001241061307200152415ustar00rootroot00000000000000serve-favicon-2.1.5/test/fixtures/000077500000000000000000000000001241061307200171125ustar00rootroot00000000000000serve-favicon-2.1.5/test/fixtures/favicon.ico000066400000000000000000000025761241061307200212450ustar00rootroot00000000000000h( }@_UC˩~6Gcc<ep›lu4ơu            serve-favicon-2.1.5/test/test.js000066400000000000000000000205061241061307200165610ustar00rootroot00000000000000 var fs = require('fs'); var http = require('http'); var path = require('path'); var proxyquire = require('proxyquire'); var request = require('supertest'); var resolve = path.resolve; var should = require('should'); var favicon = proxyquire('..', { fs: {readFile: readFile} }); var fixtures = __dirname + '/fixtures'; describe('favicon()', function(){ describe('arguments', function(){ describe('path', function(){ it('should be required', function(){ favicon.bind().should.throw(/path.*required/); }) it('should accept file path', function(){ favicon.bind(null, path.join(fixtures, 'favicon.ico')).should.not.throw(); }) it('should accept buffer', function(){ favicon.bind(null, new Buffer(20)).should.not.throw(); }) it('should exist', function(){ favicon.bind(null, path.join(fixtures, 'nothing')).should.throw(/ENOENT.*nothing/); }) it('should not be dir', function(){ favicon.bind(null, fixtures).should.throw(/EISDIR.*fixtures/); }) it('should not be number', function(){ favicon.bind(null, 12).should.throw(/path.*must be.*string/); }) }) describe('options.maxAge', function(){ it('should be in cache-control', function(done){ var server = createServer(null, {maxAge: 5000}); request(server) .get('/favicon.ico') .expect('Cache-Control', 'public, max-age=5') .expect(200, done); }) it('should have a default', function(done){ var server = createServer(); request(server) .get('/favicon.ico') .expect('Cache-Control', /public, max-age=[0-9]+/) .expect(200, done); }) it('should accept 0', function(done){ var server = createServer(null, {maxAge: 0}); request(server) .get('/favicon.ico') .expect('Cache-Control', 'public, max-age=0') .expect(200, done); }) it('should accept string', function(done){ var server = createServer(null, {maxAge: '30d'}); request(server) .get('/favicon.ico') .expect('Cache-Control', 'public, max-age=2592000') .expect(200, done); }) it('should be valid delta-seconds', function(done){ var server = createServer(null, {maxAge: 1234}); request(server) .get('/favicon.ico') .expect('Cache-Control', 'public, max-age=1') .expect(200, done); }) it('should floor at 0', function(done){ var server = createServer(null, {maxAge: -4000}); request(server) .get('/favicon.ico') .expect('Cache-Control', 'public, max-age=0') .expect(200, done); }) it('should ceil at 1 year', function(done){ var server = createServer(null, {maxAge: 900000000000}); request(server) .get('/favicon.ico') .expect('Cache-Control', 'public, max-age=31536000') .expect(200, done); }) it('should accept Inifnity', function(done){ var server = createServer(null, {maxAge: Infinity}); request(server) .get('/favicon.ico') .expect('Cache-Control', 'public, max-age=31536000') .expect(200, done); }) }) }) describe('requests', function(){ var server; before(function () { server = createServer(); }); it('should serve icon', function(done){ request(server) .get('/favicon.ico') .expect('Content-Type', 'image/x-icon') .expect(200, done); }); it('should include cache-control', function(done){ request(server) .get('/favicon.ico') .expect('Cache-Control', /public/) .expect(200, done); }); it('should include etag', function(done){ request(server) .get('/favicon.ico') .expect('ETag', /"[^"]+"/) .expect(200, done); }); it('should deny POST', function(done){ request(server) .post('/favicon.ico') .expect('Allow', 'GET, HEAD, OPTIONS') .expect(405, done); }); it('should understand OPTIONS', function(done){ request(server) .options('/favicon.ico') .expect('Allow', 'GET, HEAD, OPTIONS') .expect(200, done); }); it('should understand If-None-Match', function(done){ request(server) .get('/favicon.ico') .expect(200, function(err, res){ if (err) return done(err); request(server) .get('/favicon.ico') .set('If-None-Match', res.headers.etag) .expect(304, done); }); }); it('should ignore non-favicon requests', function(done){ request(server) .get('/') .expect(404, 'oops', done); }); }); describe('icon', function(){ describe('file', function(){ var icon = path.join(fixtures, 'favicon.ico'); var server; beforeEach(function () { readFile.resetReadCount(); server = createServer(icon); }); it('should be read on first request', function(done){ request(server) .get('/favicon.ico') .expect(200, function(err){ if (err) return done(err); readFile.getReadCount(icon).should.equal(1); done(); }); }); it('should cache for second request', function(done){ request(server) .get('/favicon.ico') .expect(200, function(err){ if (err) return done(err); request(server) .get('/favicon.ico') .expect(200, function(err){ if (err) return done(err); readFile.getReadCount(icon).should.equal(1); done(); }); }); }); }); describe('file error', function(){ var icon = path.join(fixtures, 'favicon.ico'); var server; beforeEach(function () { readFile.resetReadCount(); server = createServer(icon); }); it('should next() file read errors', function(done){ readFile.setNextError(new Error('oh no')); request(server) .get('/favicon.ico') .expect(500, 'oh no', function(err){ if (err) return done(err); readFile.getReadCount(icon).should.equal(1); done(); }); }); it('should retry reading file after error', function(done){ readFile.setNextError(new Error('oh no')); request(server) .get('/favicon.ico') .expect(500, 'oh no', function(err){ if (err) return done(err); request(server) .get('/favicon.ico') .expect(200, function(err){ if (err) return done(err); readFile.getReadCount(icon).should.equal(2); done(); }); }); }); }); describe('buffer', function(){ var buf = new Buffer(20); var server; before(function () { buf.fill(35); server = createServer(buf); }); it('should be served from buffer', function(done){ request(server) .get('/favicon.ico') .expect('Content-Length', buf.length) .expect(200, done); }); it('should be copied', function(done){ buf.fill(46); request(server) .get('/favicon.ico') .expect('Content-Length', buf.length) .expect(200, '####################', done); }); }); }); }); function createServer(icon, opts) { icon = icon || path.join(fixtures, 'favicon.ico'); var _favicon = favicon(icon, opts); var server = http.createServer(function onRequest(req, res) { _favicon(req, res, function onNext(err) { res.statusCode = err ? (err.status || 500) : 404; res.end(err ? err.message : 'oops'); }); }); return server; } function readFile(path, options, callback) { var key = resolve(path); readFile._readCount[key] = (readFile._readCount[key] || 0) + 1; if (readFile._nextError) { var cb = callback || options; var err = readFile._nextError; readFile._nextError = null; return cb (err); } return fs.readFile.apply(this, arguments); } readFile._nextError = null; readFile._readCount = Object.create(null); readFile.getReadCount = function getReadCount(path) { var key = resolve(path); return readFile._readCount[key] || 0; }; readFile.resetReadCount = function resetReadCount() { readFile._readCount = Object.create(null); }; readFile.setNextError = function setNextError(err) { readFile._nextError = err; };