Skip to content

Commit

Permalink
Add weak ETag generation
Browse files Browse the repository at this point in the history
  • Loading branch information
dougwilson committed Aug 25, 2014
1 parent cafd2cb commit ed35a00
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 21 deletions.
1 change: 1 addition & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ unreleased
==========

* Add fast-path for empty entity
* Add weak ETag generation
* Shrink size of generated ETags

1.0.1 / 2014-08-24
Expand Down
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,25 @@ $ npm install etag
var etag = require('etag')
```

### etag(str)
### etag(entity, [options])

Generate a strong ETag for the given string. This string should be the
complete body and is assumed to be UTF-8.
Generate a strong ETag for the given entity. This should be the complete
body of the entity. Both strings are `Buffer`s are accepted. By default,
a string will generate a weak ETag while a `Buffer` will generate a strong
ETag (this can be overwritten by `options.weak`).

```js
res.setHeader('ETag', etag(body))
```

### etag(buf)
#### Options

Generate a strong ETag for the given `Buffer`. This buffer should be the
complete body.
`etag` accepts these properties in the options object.

```js
res.setHeader('ETag', etag(buf))
```
##### weak

Specifies if a "strong" or a "weak" ETag will be generated. The ETag can only
really be a strong as the given input.

## Testing

Expand Down
54 changes: 47 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,20 @@ module.exports = etag
* Module dependencies.
*/

var crc = require('crc').crc32
var crypto = require('crypto')

/**
* Create a simple ETag.
*
* @param {string|Buffer} entity
* @param {object} [options]
* @param {boolean} [options.weak]
* @return {String}
* @api public
*/

function etag(entity) {
function etag(entity, options) {
if (entity == null) {
throw new TypeError('argument entity is required')
}
Expand All @@ -35,14 +38,51 @@ function etag(entity) {
throw new TypeError('argument entity must be string or Buffer')
}

if (entity.length === 0) {
// fast-path empty body
return '"1B2M2Y8AsgTpgAmY7PhCfg=="'
var buf = !isBuffer
? new Buffer(entity, 'utf8')
: entity
var weak = options && typeof options.weak === 'boolean'
? options.weak
: !isBuffer

return weak
? 'W/"' + weakhash(buf) + '"'
: '"' + stronghash(buf) + '"'
}

/**
* Generate a strong hash.
*
* @param {Buffer} entity
* @return {String}
* @api private
*/

function stronghash(buf) {
if (buf.length === 0) {
// fast-path empty
return '1B2M2Y8AsgTpgAmY7PhCfg=='
}

var hash = crypto
return crypto
.createHash('md5')
.update(entity, 'utf8')
.update(buf)
.digest('base64')
return '"' + hash + '"'
}

/**
* Generate a weak hash.
*
* @param {Buffer} entity
* @return {String}
* @api private
*/

function weakhash(buf) {
if (buf.length === 0) {
// fast-path empty
return '0-0'
}

return buf.length.toString(16) + '-' + crc(buf).toString(16)
}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"res"
],
"repository": "jshttp/etag",
"dependencies": {
"crc": "2.1.1"
},
"devDependencies": {
"istanbul": "0.3.0",
"mocha": "~1.21.4"
Expand Down
32 changes: 27 additions & 5 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,48 @@ describe('etag(entity)', function () {
})

describe('when "entity" is a string', function () {
it('should generate an ETag', function () {
assert.equal(etag('beep boop'), '"Z34SGyQ2IB7YzB7HMkCjrQ=="')
it('should generate a weak ETag', function () {
assert.equal(etag('beep boop'), 'W/"9-7f3ee715"')
})

it('should work containing Unicode', function () {
assert.equal(etag('论'), '"aW9HeLTk2Yt6lf7zJYElgw=="')
assert.equal(etag('论'), 'W/"3-438093ff"')
})

it('should work for empty string', function () {
assert.equal(etag(''), '"1B2M2Y8AsgTpgAmY7PhCfg=="')
assert.equal(etag(''), 'W/"0-0"')
})
})

describe('when "entity" is a Buffer', function () {
it('should generate an ETag', function () {
it('should generate a strong ETag', function () {
assert.equal(etag(new Buffer([1, 2, 3])), '"Uonfc331cyb83SJZevsfrA=="')
})

it('should work for empty Buffer', function () {
assert.equal(etag(new Buffer(0)), '"1B2M2Y8AsgTpgAmY7PhCfg=="')
})
})

describe('with "weak" option', function () {
describe('when "false"', function () {
it('should generate a strong ETag for a string', function () {
assert.equal(etag('beep boop', {weak: false}), '"Z34SGyQ2IB7YzB7HMkCjrQ=="')
})

it('should generate a strong ETag for a Buffer', function () {
assert.equal(etag(new Buffer([1, 2, 3]), {weak: false}), '"Uonfc331cyb83SJZevsfrA=="')
})
})

describe('when "true"', function () {
it('should generate a strong ETag for a string', function () {
assert.equal(etag('beep boop', {weak: true}), 'W/"9-7f3ee715"')
})

it('should generate a strong ETag for a Buffer', function () {
assert.equal(etag(new Buffer([1, 2, 3]), {weak: true}), 'W/"3-55bc801d"')
})
})
})
})

0 comments on commit ed35a00

Please sign in to comment.