cookie.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // src/utils/cookie.ts
  2. import { decodeURIComponent_, tryDecode } from "./url.js";
  3. var algorithm = { name: "HMAC", hash: "SHA-256" };
  4. var getCryptoKey = async (secret) => {
  5. const secretBuf = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
  6. return await crypto.subtle.importKey("raw", secretBuf, algorithm, false, ["sign", "verify"]);
  7. };
  8. var makeSignature = async (value, secret) => {
  9. const key = await getCryptoKey(secret);
  10. const signature = await crypto.subtle.sign(algorithm.name, key, new TextEncoder().encode(value));
  11. return btoa(String.fromCharCode(...new Uint8Array(signature)));
  12. };
  13. var verifySignature = async (base64Signature, value, secret) => {
  14. try {
  15. const signatureBinStr = atob(base64Signature);
  16. const signature = new Uint8Array(signatureBinStr.length);
  17. for (let i = 0, len = signatureBinStr.length; i < len; i++) {
  18. signature[i] = signatureBinStr.charCodeAt(i);
  19. }
  20. return await crypto.subtle.verify(algorithm, secret, signature, new TextEncoder().encode(value));
  21. } catch {
  22. return false;
  23. }
  24. };
  25. var validCookieNameRegEx = /^[\w!#$%&'*.^`|~+-]+$/;
  26. var validCookieValueRegEx = /^[ !#-:<-[\]-~]*$/;
  27. var trimCookieWhitespace = (value) => {
  28. let start = 0;
  29. let end = value.length;
  30. while (start < end) {
  31. const charCode = value.charCodeAt(start);
  32. if (charCode !== 32 && charCode !== 9) {
  33. break;
  34. }
  35. start++;
  36. }
  37. while (end > start) {
  38. const charCode = value.charCodeAt(end - 1);
  39. if (charCode !== 32 && charCode !== 9) {
  40. break;
  41. }
  42. end--;
  43. }
  44. return start === 0 && end === value.length ? value : value.slice(start, end);
  45. };
  46. var parse = (cookie, name) => {
  47. if (name && cookie.indexOf(name) === -1) {
  48. return {};
  49. }
  50. const pairs = cookie.split(";");
  51. const parsedCookie = {};
  52. for (const pairStr of pairs) {
  53. const valueStartPos = pairStr.indexOf("=");
  54. if (valueStartPos === -1) {
  55. continue;
  56. }
  57. const cookieName = trimCookieWhitespace(pairStr.substring(0, valueStartPos));
  58. if (name && name !== cookieName || !validCookieNameRegEx.test(cookieName)) {
  59. continue;
  60. }
  61. let cookieValue = trimCookieWhitespace(pairStr.substring(valueStartPos + 1));
  62. if (cookieValue.startsWith('"') && cookieValue.endsWith('"')) {
  63. cookieValue = cookieValue.slice(1, -1);
  64. }
  65. if (validCookieValueRegEx.test(cookieValue)) {
  66. parsedCookie[cookieName] = cookieValue.indexOf("%") !== -1 ? tryDecode(cookieValue, decodeURIComponent_) : cookieValue;
  67. if (name) {
  68. break;
  69. }
  70. }
  71. }
  72. return parsedCookie;
  73. };
  74. var parseSigned = async (cookie, secret, name) => {
  75. const parsedCookie = {};
  76. const secretKey = await getCryptoKey(secret);
  77. for (const [key, value] of Object.entries(parse(cookie, name))) {
  78. const signatureStartPos = value.lastIndexOf(".");
  79. if (signatureStartPos < 1) {
  80. continue;
  81. }
  82. const signedValue = value.substring(0, signatureStartPos);
  83. const signature = value.substring(signatureStartPos + 1);
  84. if (signature.length !== 44 || !signature.endsWith("=")) {
  85. continue;
  86. }
  87. const isVerified = await verifySignature(signature, signedValue, secretKey);
  88. parsedCookie[key] = isVerified ? signedValue : false;
  89. }
  90. return parsedCookie;
  91. };
  92. var _serialize = (name, value, opt = {}) => {
  93. if (!validCookieNameRegEx.test(name)) {
  94. throw new Error("Invalid cookie name");
  95. }
  96. let cookie = `${name}=${value}`;
  97. if (name.startsWith("__Secure-") && !opt.secure) {
  98. throw new Error("__Secure- Cookie must have Secure attributes");
  99. }
  100. if (name.startsWith("__Host-")) {
  101. if (!opt.secure) {
  102. throw new Error("__Host- Cookie must have Secure attributes");
  103. }
  104. if (opt.path !== "/") {
  105. throw new Error('__Host- Cookie must have Path attributes with "/"');
  106. }
  107. if (opt.domain) {
  108. throw new Error("__Host- Cookie must not have Domain attributes");
  109. }
  110. }
  111. for (const key of ["domain", "path"]) {
  112. if (opt[key] && /[;\r\n]/.test(opt[key])) {
  113. throw new Error(`${key} must not contain ";", "\\r", or "\\n"`);
  114. }
  115. }
  116. if (opt && typeof opt.maxAge === "number" && opt.maxAge >= 0) {
  117. if (opt.maxAge > 3456e4) {
  118. throw new Error(
  119. "Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration."
  120. );
  121. }
  122. cookie += `; Max-Age=${opt.maxAge | 0}`;
  123. }
  124. if (opt.domain && opt.prefix !== "host") {
  125. cookie += `; Domain=${opt.domain}`;
  126. }
  127. if (opt.path) {
  128. cookie += `; Path=${opt.path}`;
  129. }
  130. if (opt.expires) {
  131. if (opt.expires.getTime() - Date.now() > 3456e7) {
  132. throw new Error(
  133. "Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future."
  134. );
  135. }
  136. cookie += `; Expires=${opt.expires.toUTCString()}`;
  137. }
  138. if (opt.httpOnly) {
  139. cookie += "; HttpOnly";
  140. }
  141. if (opt.secure) {
  142. cookie += "; Secure";
  143. }
  144. if (opt.sameSite) {
  145. cookie += `; SameSite=${opt.sameSite.charAt(0).toUpperCase() + opt.sameSite.slice(1)}`;
  146. }
  147. if (opt.priority) {
  148. cookie += `; Priority=${opt.priority.charAt(0).toUpperCase() + opt.priority.slice(1)}`;
  149. }
  150. if (opt.partitioned) {
  151. if (!opt.secure) {
  152. throw new Error("Partitioned Cookie must have Secure attributes");
  153. }
  154. cookie += "; Partitioned";
  155. }
  156. return cookie;
  157. };
  158. var serialize = (name, value, opt) => {
  159. value = encodeURIComponent(value);
  160. return _serialize(name, value, opt);
  161. };
  162. var serializeSigned = async (name, value, secret, opt = {}) => {
  163. const signature = await makeSignature(value, secret);
  164. value = `${value}.${signature}`;
  165. value = encodeURIComponent(value);
  166. return _serialize(name, value, opt);
  167. };
  168. export {
  169. parse,
  170. parseSigned,
  171. serialize,
  172. serializeSigned
  173. };