You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

171 lines
3.2 KiB

/*!
* etag
* Copyright(c) 2014 Douglas Christopher Wilson
* MIT Licensed
*/
/**
* Module exports.
*/
module.exports = etag
/**
* Module dependencies.
*/
var crc = require('crc').crc32
var crypto = require('crypto')
var Stats = require('fs').Stats
/**
* Module variables.
*/
var crc32threshold = 1000 // 1KB
var NULL = new Buffer([0])
var toString = Object.prototype.toString
/**
* Create a simple ETag.
*
* @param {string|Buffer|Stats} entity
* @param {object} [options]
* @param {boolean} [options.weak]
* @return {String}
* @api public
*/
function etag(entity, options) {
if (entity == null) {
throw new TypeError('argument entity is required')
}
var isStats = isstats(entity)
var weak = options && typeof options.weak === 'boolean'
? options.weak
: isStats
// support fs.Stats object
if (isStats) {
return stattag(entity, weak)
}
if (typeof entity !== 'string' && !Buffer.isBuffer(entity)) {
throw new TypeError('argument entity must be string, Buffer, or fs.Stats')
}
var hash = weak
? weakhash(entity)
: stronghash(entity)
return weak
? 'W/"' + hash + '"'
: '"' + hash + '"'
}
/**
* Determine if object is a Stats object.
*
* @param {object} obj
* @return {boolean}
* @api private
*/
function isstats(obj) {
// not even an object
if (obj === null || typeof obj !== 'object') {
return false
}
// genuine fs.Stats
if (obj instanceof Stats) {
return true
}
// quack quack
return 'atime' in obj && toString.call(obj.atime) === '[object Date]'
&& 'ctime' in obj && toString.call(obj.ctime) === '[object Date]'
&& 'mtime' in obj && toString.call(obj.mtime) === '[object Date]'
&& 'ino' in obj && typeof obj.ino === 'number'
&& 'size' in obj && typeof obj.size === 'number'
}
/**
* Generate a tag for a stat.
*
* @param {Buffer} entity
* @return {String}
* @api private
*/
function stattag(stat, weak) {
var mtime = stat.mtime.toISOString()
var size = stat.size.toString(16)
if (weak) {
return 'W/"' + size + '-' + crc(mtime) + '"'
}
var hash = crypto
.createHash('md5')
.update('file', 'utf8')
.update(NULL)
.update(size, 'utf8')
.update(NULL)
.update(mtime, 'utf8')
.digest('base64')
return '"' + hash + '"'
}
/**
* Generate a strong hash.
*
* @param {Buffer} entity
* @return {String}
* @api private
*/
function stronghash(entity) {
if (entity.length === 0) {
// fast-path empty
return '1B2M2Y8AsgTpgAmY7PhCfg=='
}
return crypto
.createHash('md5')
.update(entity, 'utf8')
.digest('base64')
}
/**
* Generate a weak hash.
*
* @param {Buffer} entity
* @return {String}
* @api private
*/
function weakhash(entity) {
if (entity.length === 0) {
// fast-path empty
return '0-0'
}
var len = typeof entity === 'string'
? Buffer.byteLength(entity, 'utf8')
: entity.length
if (len <= crc32threshold) {
// crc32 plus length when it's fast
// crc(str) only accepts utf-8 encoding
return len.toString(16) + '-' + crc(entity).toString(16)
}
// use md4 for long strings
return crypto
.createHash('md4')
.update(entity, 'utf8')
.digest('base64')
}