index.js 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. /*!
  2. * fresh
  3. * Copyright(c) 2012 TJ Holowaychuk
  4. * Copyright(c) 2016-2017 Douglas Christopher Wilson
  5. * MIT Licensed
  6. */
  7. 'use strict'
  8. /**
  9. * RegExp to check for no-cache token in Cache-Control.
  10. * @private
  11. */
  12. var CACHE_CONTROL_NO_CACHE_REGEXP = /(?:^|,)\s*?no-cache\s*?(?:,|$)/
  13. /**
  14. * Module exports.
  15. * @public
  16. */
  17. module.exports = fresh
  18. /**
  19. * Check freshness of the response using request and response headers.
  20. *
  21. * @param {Object} reqHeaders
  22. * @param {Object} resHeaders
  23. * @return {Boolean}
  24. * @public
  25. */
  26. function fresh (reqHeaders, resHeaders) {
  27. // fields
  28. var modifiedSince = reqHeaders['if-modified-since']
  29. var noneMatch = reqHeaders['if-none-match']
  30. // unconditional request
  31. if (!modifiedSince && !noneMatch) {
  32. return false
  33. }
  34. // Always return stale when Cache-Control: no-cache
  35. // to support end-to-end reload requests
  36. // https://tools.ietf.org/html/rfc2616#section-14.9.4
  37. var cacheControl = reqHeaders['cache-control']
  38. if (cacheControl && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl)) {
  39. return false
  40. }
  41. // if-none-match
  42. if (noneMatch && noneMatch !== '*') {
  43. var etag = resHeaders['etag']
  44. if (!etag) {
  45. return false
  46. }
  47. var etagStale = true
  48. var matches = parseTokenList(noneMatch)
  49. for (var i = 0; i < matches.length; i++) {
  50. var match = matches[i]
  51. if (match === etag || match === 'W/' + etag || 'W/' + match === etag) {
  52. etagStale = false
  53. break
  54. }
  55. }
  56. if (etagStale) {
  57. return false
  58. }
  59. }
  60. // if-modified-since
  61. if (modifiedSince) {
  62. var lastModified = resHeaders['last-modified']
  63. var modifiedStale = !lastModified || !(parseHttpDate(lastModified) <= parseHttpDate(modifiedSince))
  64. if (modifiedStale) {
  65. return false
  66. }
  67. }
  68. return true
  69. }
  70. /**
  71. * Parse an HTTP Date into a number.
  72. *
  73. * @param {string} date
  74. * @private
  75. */
  76. function parseHttpDate (date) {
  77. var timestamp = date && Date.parse(date)
  78. // istanbul ignore next: guard against date.js Date.parse patching
  79. return typeof timestamp === 'number'
  80. ? timestamp
  81. : NaN
  82. }
  83. /**
  84. * Parse a HTTP token list.
  85. *
  86. * @param {string} str
  87. * @private
  88. */
  89. function parseTokenList (str) {
  90. var end = 0
  91. var list = []
  92. var start = 0
  93. // gather tokens
  94. for (var i = 0, len = str.length; i < len; i++) {
  95. switch (str.charCodeAt(i)) {
  96. case 0x20: /* */
  97. if (start === end) {
  98. start = end = i + 1
  99. }
  100. break
  101. case 0x2c: /* , */
  102. list.push(str.substring(start, end))
  103. start = end = i + 1
  104. break
  105. default:
  106. end = i + 1
  107. break
  108. }
  109. }
  110. // final token
  111. list.push(str.substring(start, end))
  112. return list
  113. }