regexes.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import * as util from "./util.js";
  2. export const cuid = /^[cC][^\s-]{8,}$/;
  3. export const cuid2 = /^[0-9a-z]+$/;
  4. export const ulid = /^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/;
  5. export const xid = /^[0-9a-vA-V]{20}$/;
  6. export const ksuid = /^[A-Za-z0-9]{27}$/;
  7. export const nanoid = /^[a-zA-Z0-9_-]{21}$/;
  8. /** ISO 8601-1 duration regex. Does not support the 8601-2 extensions like negative durations or fractional/negative components. */
  9. export const duration = /^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/;
  10. /** Implements ISO 8601-2 extensions like explicit +- prefixes, mixing weeks with other units, and fractional/negative components. */
  11. export const extendedDuration = /^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/;
  12. /** A regex for any UUID-like identifier: 8-4-4-4-12 hex pattern */
  13. export const guid = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;
  14. /** Returns a regex for validating an RFC 9562/4122 UUID.
  15. *
  16. * @param version Optionally specify a version 1-8. If no version is specified, all versions are supported. */
  17. export const uuid = (version) => {
  18. if (!version)
  19. return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;
  20. return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`);
  21. };
  22. export const uuid4 = /*@__PURE__*/ uuid(4);
  23. export const uuid6 = /*@__PURE__*/ uuid(6);
  24. export const uuid7 = /*@__PURE__*/ uuid(7);
  25. /** Practical email validation */
  26. export const email = /^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/;
  27. /** Equivalent to the HTML5 input[type=email] validation implemented by browsers. Source: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email */
  28. export const html5Email = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
  29. /** The classic emailregex.com regex for RFC 5322-compliant emails */
  30. export const rfc5322Email = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
  31. /** A loose regex that allows Unicode characters, enforces length limits, and that's about it. */
  32. export const unicodeEmail = /^[^\s@"]{1,64}@[^\s@]{1,255}$/u;
  33. export const idnEmail = unicodeEmail;
  34. export const browserEmail = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
  35. // from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression
  36. const _emoji = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`;
  37. export function emoji() {
  38. return new RegExp(_emoji, "u");
  39. }
  40. export const ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
  41. export const ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
  42. export const mac = (delimiter) => {
  43. const escapedDelim = util.escapeRegex(delimiter ?? ":");
  44. return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
  45. };
  46. export const cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
  47. export const cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
  48. // https://stackoverflow.com/questions/7860392/determine-if-string-is-in-base64-using-javascript
  49. export const base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
  50. export const base64url = /^[A-Za-z0-9_-]*$/;
  51. // based on https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
  52. // export const hostname: RegExp = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
  53. export const hostname = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
  54. export const domain = /^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
  55. // https://blog.stevenlevithan.com/archives/validate-phone-number#r4-3 (regex sans spaces)
  56. // E.164: leading digit must be 1-9; total digits (excluding '+') between 7-15
  57. export const e164 = /^\+[1-9]\d{6,14}$/;
  58. // const dateSource = `((\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\\d|3[01])|(0[469]|11)-(0[1-9]|[12]\\d|30)|(02)-(0[1-9]|1\\d|2[0-8])))`;
  59. const dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`;
  60. export const date = /*@__PURE__*/ new RegExp(`^${dateSource}$`);
  61. function timeSource(args) {
  62. const hhmm = `(?:[01]\\d|2[0-3]):[0-5]\\d`;
  63. const regex = typeof args.precision === "number"
  64. ? args.precision === -1
  65. ? `${hhmm}`
  66. : args.precision === 0
  67. ? `${hhmm}:[0-5]\\d`
  68. : `${hhmm}:[0-5]\\d\\.\\d{${args.precision}}`
  69. : `${hhmm}(?::[0-5]\\d(?:\\.\\d+)?)?`;
  70. return regex;
  71. }
  72. export function time(args) {
  73. return new RegExp(`^${timeSource(args)}$`);
  74. }
  75. // Adapted from https://stackoverflow.com/a/3143231
  76. export function datetime(args) {
  77. const time = timeSource({ precision: args.precision });
  78. const opts = ["Z"];
  79. if (args.local)
  80. opts.push("");
  81. // if (args.offset) opts.push(`([+-]\\d{2}:\\d{2})`);
  82. if (args.offset)
  83. opts.push(`([+-](?:[01]\\d|2[0-3]):[0-5]\\d)`);
  84. const timeRegex = `${time}(?:${opts.join("|")})`;
  85. return new RegExp(`^${dateSource}T(?:${timeRegex})$`);
  86. }
  87. export const string = (params) => {
  88. const regex = params ? `[\\s\\S]{${params?.minimum ?? 0},${params?.maximum ?? ""}}` : `[\\s\\S]*`;
  89. return new RegExp(`^${regex}$`);
  90. };
  91. export const bigint = /^-?\d+n?$/;
  92. export const integer = /^-?\d+$/;
  93. export const number = /^-?\d+(?:\.\d+)?$/;
  94. export const boolean = /^(?:true|false)$/i;
  95. const _null = /^null$/i;
  96. export { _null as null };
  97. const _undefined = /^undefined$/i;
  98. export { _undefined as undefined };
  99. // regex for string with no uppercase letters
  100. export const lowercase = /^[^A-Z]*$/;
  101. // regex for string with no lowercase letters
  102. export const uppercase = /^[^a-z]*$/;
  103. // regex for hexadecimal strings (any length)
  104. export const hex = /^[0-9a-fA-F]*$/;
  105. // Hash regexes for different algorithms and encodings
  106. // Helper function to create base64 regex with exact length and padding
  107. function fixedBase64(bodyLength, padding) {
  108. return new RegExp(`^[A-Za-z0-9+/]{${bodyLength}}${padding}$`);
  109. }
  110. // Helper function to create base64url regex with exact length (no padding)
  111. function fixedBase64url(length) {
  112. return new RegExp(`^[A-Za-z0-9_-]{${length}}$`);
  113. }
  114. // MD5 (16 bytes): base64 = 24 chars total (22 + "==")
  115. export const md5_hex = /^[0-9a-fA-F]{32}$/;
  116. export const md5_base64 = /*@__PURE__*/ fixedBase64(22, "==");
  117. export const md5_base64url = /*@__PURE__*/ fixedBase64url(22);
  118. // SHA1 (20 bytes): base64 = 28 chars total (27 + "=")
  119. export const sha1_hex = /^[0-9a-fA-F]{40}$/;
  120. export const sha1_base64 = /*@__PURE__*/ fixedBase64(27, "=");
  121. export const sha1_base64url = /*@__PURE__*/ fixedBase64url(27);
  122. // SHA256 (32 bytes): base64 = 44 chars total (43 + "=")
  123. export const sha256_hex = /^[0-9a-fA-F]{64}$/;
  124. export const sha256_base64 = /*@__PURE__*/ fixedBase64(43, "=");
  125. export const sha256_base64url = /*@__PURE__*/ fixedBase64url(43);
  126. // SHA384 (48 bytes): base64 = 64 chars total (no padding)
  127. export const sha384_hex = /^[0-9a-fA-F]{96}$/;
  128. export const sha384_base64 = /*@__PURE__*/ fixedBase64(64, "");
  129. export const sha384_base64url = /*@__PURE__*/ fixedBase64url(64);
  130. // SHA512 (64 bytes): base64 = 88 chars total (86 + "==")
  131. export const sha512_hex = /^[0-9a-fA-F]{128}$/;
  132. export const sha512_base64 = /*@__PURE__*/ fixedBase64(86, "==");
  133. export const sha512_base64url = /*@__PURE__*/ fixedBase64url(86);