index.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. function intToString(intValue) {
  2. return String(intValue);
  3. }
  4. function stringToInt(strValue) {
  5. return parseInt(strValue, 10) || 0;
  6. }
  7. // Don’t send `SameSite=None` to known incompatible clients.
  8. function isSameSiteNoneCompatible(useragent) {
  9. return !isSameSiteNoneIncompatible(String(useragent));
  10. }
  11. // Classes of browsers known to be incompatible.
  12. function isSameSiteNoneIncompatible(useragent) {
  13. return (
  14. hasWebKitSameSiteBug(useragent) ||
  15. dropsUnrecognizedSameSiteCookies(useragent)
  16. );
  17. }
  18. function hasWebKitSameSiteBug(useragent) {
  19. return (
  20. isIosVersion(12, useragent) ||
  21. (isMacosxVersion(10, 14, useragent) &&
  22. (isSafari(useragent) || isMacEmbeddedBrowser(useragent)))
  23. );
  24. }
  25. function dropsUnrecognizedSameSiteCookies(useragent) {
  26. return (
  27. (isChromiumBased(useragent) &&
  28. isChromiumVersionAtLeast(51, useragent) &&
  29. !isChromiumVersionAtLeast(67, useragent)) ||
  30. (isUcBrowser(useragent) && !isUcBrowserVersionAtLeast(12, 13, 2, useragent))
  31. );
  32. }
  33. // Regex parsing of User-Agent string.
  34. function regexContains(stringValue, regex) {
  35. var matches = stringValue.match(regex);
  36. return matches !== null;
  37. }
  38. function extractRegexMatch(stringValue, regex, offsetIndex) {
  39. var matches = stringValue.match(regex);
  40. if (matches !== null && matches[offsetIndex] !== undefined) {
  41. return matches[offsetIndex];
  42. }
  43. return null;
  44. }
  45. function isIosVersion(major, useragent) {
  46. var regex = /\(iP.+; CPU .*OS (\d+)[_\d]*.*\) AppleWebKit\//;
  47. // Extract digits from first capturing group.
  48. return extractRegexMatch(useragent, regex, 1) === intToString(major);
  49. }
  50. function isMacosxVersion(major, minor, useragent) {
  51. var regex = /\(Macintosh;.*Mac OS X (\d+)_(\d+)[_\d]*.*\) AppleWebKit\//;
  52. // Extract digits from first and second capturing groups.
  53. return (
  54. extractRegexMatch(useragent, regex, 1) === intToString(major) &&
  55. extractRegexMatch(useragent, regex, 2) === intToString(minor)
  56. );
  57. }
  58. function isSafari(useragent) {
  59. var safari_regex = /Version\/.* Safari\//;
  60. return useragent.match(safari_regex) !== null && !isChromiumBased(useragent);
  61. }
  62. function isMacEmbeddedBrowser(useragent) {
  63. var regex = /^Mozilla\/[\.\d]+ \(Macintosh;.*Mac OS X [_\d]+\) AppleWebKit\/[\.\d]+ \(KHTML, like Gecko\)$/;
  64. return regexContains(useragent, regex);
  65. }
  66. function isChromiumBased(useragent) {
  67. const regex = /Chrom(e|ium)/;
  68. return regexContains(useragent, regex);
  69. }
  70. function isChromiumVersionAtLeast(major, useragent) {
  71. var regex = /Chrom[^ \/]+\/(\d+)[\.\d]* /;
  72. // Extract digits from first capturing group.
  73. var version = stringToInt(extractRegexMatch(useragent, regex, 1));
  74. return version >= major;
  75. }
  76. function isUcBrowser(useragent) {
  77. var regex = /UCBrowser\//;
  78. return regexContains(useragent, regex);
  79. }
  80. function isUcBrowserVersionAtLeast(major, minor, build, useragent) {
  81. var regex = /UCBrowser\/(\d+)\.(\d+)\.(\d+)[\.\d]* /;
  82. // Extract digits from three capturing groups.
  83. var major_version = stringToInt(extractRegexMatch(useragent, regex, 1));
  84. var minor_version = stringToInt(extractRegexMatch(useragent, regex, 2));
  85. var build_version = stringToInt(extractRegexMatch(useragent, regex, 3));
  86. if (major_version !== major) {
  87. return major_version > major;
  88. }
  89. if (minor_version != minor) {
  90. return minor_version > minor;
  91. }
  92. return build_version >= build;
  93. }
  94. var shouldSendSameSiteNone = function(req, res, next) {
  95. var writeHead = res.writeHead;
  96. res.writeHead = function() {
  97. var ua = req.get("user-agent");
  98. var isCompatible = isSameSiteNoneCompatible(ua);
  99. var cookies = res.get("Set-Cookie");
  100. var removeSameSiteNone = function(str) {
  101. return str.replace(/;\s*SameSite\s*=\s*None\s*(?=;|$)/ig, "");
  102. };
  103. if (!isCompatible && cookies) {
  104. if (Array.isArray(cookies)) {
  105. cookies = cookies.map(removeSameSiteNone);
  106. } else {
  107. cookies = removeSameSiteNone(cookies);
  108. }
  109. res.set("Set-Cookie", cookies);
  110. }
  111. writeHead.apply(this, arguments);
  112. };
  113. next();
  114. };
  115. module.exports = {
  116. shouldSendSameSiteNone: shouldSendSameSiteNone,
  117. isSameSiteNoneCompatible: isSameSiteNoneCompatible
  118. };