index.js (2479B)
1 /*! 2 * etag 3 * Copyright(c) 2014-2016 Douglas Christopher Wilson 4 * MIT Licensed 5 */ 6 7 'use strict' 8 9 /** 10 * Module exports. 11 * @public 12 */ 13 14 module.exports = etag 15 16 /** 17 * Module dependencies. 18 * @private 19 */ 20 21 var crypto = require('crypto') 22 var Stats = require('fs').Stats 23 24 /** 25 * Module variables. 26 * @private 27 */ 28 29 var toString = Object.prototype.toString 30 31 /** 32 * Generate an entity tag. 33 * 34 * @param {Buffer|string} entity 35 * @return {string} 36 * @private 37 */ 38 39 function entitytag (entity) { 40 if (entity.length === 0) { 41 // fast-path empty 42 return '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"' 43 } 44 45 // compute hash of entity 46 var hash = crypto 47 .createHash('sha1') 48 .update(entity, 'utf8') 49 .digest('base64') 50 .substring(0, 27) 51 52 // compute length of entity 53 var len = typeof entity === 'string' 54 ? Buffer.byteLength(entity, 'utf8') 55 : entity.length 56 57 return '"' + len.toString(16) + '-' + hash + '"' 58 } 59 60 /** 61 * Create a simple ETag. 62 * 63 * @param {string|Buffer|Stats} entity 64 * @param {object} [options] 65 * @param {boolean} [options.weak] 66 * @return {String} 67 * @public 68 */ 69 70 function etag (entity, options) { 71 if (entity == null) { 72 throw new TypeError('argument entity is required') 73 } 74 75 // support fs.Stats object 76 var isStats = isstats(entity) 77 var weak = options && typeof options.weak === 'boolean' 78 ? options.weak 79 : isStats 80 81 // validate argument 82 if (!isStats && typeof entity !== 'string' && !Buffer.isBuffer(entity)) { 83 throw new TypeError('argument entity must be string, Buffer, or fs.Stats') 84 } 85 86 // generate entity tag 87 var tag = isStats 88 ? stattag(entity) 89 : entitytag(entity) 90 91 return weak 92 ? 'W/' + tag 93 : tag 94 } 95 96 /** 97 * Determine if object is a Stats object. 98 * 99 * @param {object} obj 100 * @return {boolean} 101 * @api private 102 */ 103 104 function isstats (obj) { 105 // genuine fs.Stats 106 if (typeof Stats === 'function' && obj instanceof Stats) { 107 return true 108 } 109 110 // quack quack 111 return obj && typeof obj === 'object' && 112 'ctime' in obj && toString.call(obj.ctime) === '[object Date]' && 113 'mtime' in obj && toString.call(obj.mtime) === '[object Date]' && 114 'ino' in obj && typeof obj.ino === 'number' && 115 'size' in obj && typeof obj.size === 'number' 116 } 117 118 /** 119 * Generate a tag for a stat. 120 * 121 * @param {object} stat 122 * @return {string} 123 * @private 124 */ 125 126 function stattag (stat) { 127 var mtime = stat.mtime.getTime().toString(16) 128 var size = stat.size.toString(16) 129 130 return '"' + size + '-' + mtime + '"' 131 }