index.js 2.4 KB

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