index.js 2.5 KB

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