index.js 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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 takes precedent over if-modified-since
  42. if (noneMatch) {
  43. if (noneMatch === '*') {
  44. return true
  45. }
  46. var etag = resHeaders.etag
  47. if (!etag) {
  48. return false
  49. }
  50. var matches = parseTokenList(noneMatch)
  51. for (var i = 0; i < matches.length; i++) {
  52. var match = matches[i]
  53. if (match === etag || match === 'W/' + etag || 'W/' + match === etag) {
  54. return true
  55. }
  56. }
  57. return false
  58. }
  59. // if-modified-since
  60. if (modifiedSince) {
  61. var lastModified = resHeaders['last-modified']
  62. var modifiedStale = !lastModified || !(parseHttpDate(lastModified) <= parseHttpDate(modifiedSince))
  63. if (modifiedStale) {
  64. return false
  65. }
  66. }
  67. return true
  68. }
  69. /**
  70. * Parse an HTTP Date into a number.
  71. *
  72. * @param {string} date
  73. * @private
  74. */
  75. function parseHttpDate (date) {
  76. var timestamp = date && Date.parse(date)
  77. // istanbul ignore next: guard against date.js Date.parse patching
  78. return typeof timestamp === 'number'
  79. ? timestamp
  80. : NaN
  81. }
  82. /**
  83. * Parse a HTTP token list.
  84. *
  85. * @param {string} str
  86. * @private
  87. */
  88. function parseTokenList (str) {
  89. var end = 0
  90. var list = []
  91. var start = 0
  92. // gather tokens
  93. for (var i = 0, len = str.length; i < len; i++) {
  94. switch (str.charCodeAt(i)) {
  95. case 0x20: /* */
  96. if (start === end) {
  97. start = end = i + 1
  98. }
  99. break
  100. case 0x2c: /* , */
  101. list.push(str.substring(start, end))
  102. start = end = i + 1
  103. break
  104. default:
  105. end = i + 1
  106. break
  107. }
  108. }
  109. // final token
  110. list.push(str.substring(start, end))
  111. return list
  112. }