validator.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. library validator;
  2. import 'dart:core';
  3. /// The Type enum
  4. ///
  5. /// The domain type is either None, Alphabetic, Numeric or AlphaNumeric
  6. enum SubdomainType { None, Alphabetic, Numeric, AlphaNumeric }
  7. ///The EmailValidator entry point
  8. ///
  9. /// To use the EmailValidator class, call EmailValidator.methodName
  10. class EmailValidator {
  11. // An atomic index which is reused during iterations in different methods
  12. static int _index = 0;
  13. // A string character set containing all special characters
  14. static const String _atomCharacters = "!#\$%&'*+-/=?^_`{|}~";
  15. // Sets default domainType to null on initialization
  16. static SubdomainType _domainType = SubdomainType.None;
  17. // Returns true if the first letter in string c has a 16-bit UTF-16 code unit
  18. // greater than or equal to 48 and less than or equal to 57
  19. // otherwise return false
  20. static bool _isDigit(String c) {
  21. return c.codeUnitAt(0) >= 48 && c.codeUnitAt(0) <= 57;
  22. }
  23. // Returns true if the first letter in string c has a 16-bit UTF-16 code unit
  24. // greater than or equal to 65 and less than or equal to 90 (capital letters)
  25. // or greater than or equal to 97 and less than or equal to 122 (lowercase letters)
  26. // otherwise return false
  27. static bool _isLetter(String c) {
  28. return (c.codeUnitAt(0) >= 65 && c.codeUnitAt(0) <= 90) ||
  29. (c.codeUnitAt(0) >= 97 && c.codeUnitAt(0) <= 122);
  30. }
  31. // Returns true if calling isLetter or isDigit with the same string returns true
  32. // Only returns false if both isLetter and isDigit return false
  33. static bool _isLetterOrDigit(String c) {
  34. return _isLetter(c) || _isDigit(c);
  35. }
  36. // Returns value of allowInternational if the first letter in the string c isnt a
  37. // number or letter or special character otherwise
  38. // return the result of _isLetterOrDigit or _atomCharacters.contains(c)
  39. // which only returns false if both _isLetterOrDigit and _atomCharacters.contains(c)
  40. // returns false
  41. static bool _isAtom(String c, bool allowInternational) {
  42. return c.codeUnitAt(0) < 128
  43. ? _isLetterOrDigit(c) || _atomCharacters.contains(c)
  44. : allowInternational;
  45. }
  46. // First checks whether the first letter in string c is a letter, number or special
  47. // character
  48. // If calling isLetter returns true or c is '-',
  49. // domainType is set to Alphabetic and the function returns true
  50. // If calling isDigit returns true
  51. // domainType is set to Numeric and the function returns true
  52. // Otherwise the function returns false
  53. //
  54. // If the first if statement for string c being a letter, number or special character
  55. // fails
  56. // The value of allowInternational is checked where, if true,
  57. // domainType is set to Alphabetic and the function returns true
  58. // Otherwise, the function returns false
  59. static bool _isDomain(String c, bool allowInternational) {
  60. if (c.codeUnitAt(0) < 128) {
  61. if (_isLetter(c) || c == '-') {
  62. _domainType = SubdomainType.Alphabetic;
  63. return true;
  64. }
  65. if (_isDigit(c)) {
  66. _domainType = SubdomainType.Numeric;
  67. return true;
  68. }
  69. return false;
  70. }
  71. if (allowInternational) {
  72. _domainType = SubdomainType.Alphabetic;
  73. return true;
  74. }
  75. return false;
  76. }
  77. // Returns true if domainType is not None
  78. // Otherwise returns false
  79. static bool _isDomainStart(String c, bool allowInternational) {
  80. if (c.codeUnitAt(0) < 128) {
  81. if (_isLetter(c)) {
  82. _domainType = SubdomainType.Alphabetic;
  83. return true;
  84. }
  85. if (_isDigit(c)) {
  86. _domainType = SubdomainType.Numeric;
  87. return true;
  88. }
  89. _domainType = SubdomainType.None;
  90. return false;
  91. }
  92. if (allowInternational) {
  93. _domainType = SubdomainType.Alphabetic;
  94. return true;
  95. }
  96. _domainType = SubdomainType.None;
  97. return false;
  98. }
  99. // TODO: Documentation for this function is required
  100. static bool _skipAtom(String text, bool allowInternational) {
  101. final startIndex = _index;
  102. while (_index < text.length && _isAtom(text[_index], allowInternational)) {
  103. _index++;
  104. }
  105. return _index > startIndex;
  106. }
  107. // Skips checking of subdomain and returns false if domainType is None
  108. // Otherwise returns true
  109. static bool _skipSubDomain(String text, bool allowInternational) {
  110. final startIndex = _index;
  111. if (!_isDomainStart(text[_index], allowInternational)) {
  112. return false;
  113. }
  114. _index++;
  115. while (
  116. _index < text.length && _isDomain(text[_index], allowInternational)) {
  117. _index++;
  118. }
  119. return (_index - startIndex) < 64 && text[_index - 1] != '-';
  120. }
  121. // Skips checking of domain if domainType is numeric and returns false
  122. // Otherwise, return true
  123. static bool _skipDomain(
  124. String text, bool allowTopLevelDomains, bool allowInternational) {
  125. if (!_skipSubDomain(text, allowInternational)) {
  126. return false;
  127. }
  128. if (_index < text.length && text[_index] == '.') {
  129. do {
  130. _index++;
  131. if (_index == text.length) {
  132. return false;
  133. }
  134. if (!_skipSubDomain(text, allowInternational)) {
  135. return false;
  136. }
  137. } while (_index < text.length && text[_index] == '.');
  138. } else if (!allowTopLevelDomains) {
  139. return false;
  140. }
  141. // Note: by allowing AlphaNumeric,
  142. // we get away with not having to support punycode.
  143. if (_domainType == SubdomainType.Numeric) {
  144. return false;
  145. }
  146. return true;
  147. }
  148. // Function skips over quoted text where if quoted text is in the string
  149. // the function returns true
  150. // otherwise the function returns false
  151. static bool _skipQuoted(String text, bool allowInternational) {
  152. var escaped = false;
  153. // skip over leading '"'
  154. _index++;
  155. while (_index < text.length) {
  156. if (text[_index].codeUnitAt(0) >= 128 && !allowInternational) {
  157. return false;
  158. }
  159. if (text[_index] == '\\') {
  160. escaped = !escaped;
  161. } else if (!escaped) {
  162. if (text[_index] == '"') {
  163. break;
  164. }
  165. } else {
  166. escaped = false;
  167. }
  168. _index++;
  169. }
  170. if (_index >= text.length || text[_index] != '"') {
  171. return false;
  172. }
  173. _index++;
  174. return true;
  175. }
  176. // TODO: Documentation for this function is required
  177. static bool _skipIPv4Literal(String text) {
  178. var groups = 0;
  179. while (_index < text.length && groups < 4) {
  180. final startIndex = _index;
  181. var value = 0;
  182. while (_index < text.length &&
  183. text[_index].codeUnitAt(0) >= 48 &&
  184. text[_index].codeUnitAt(0) <= 57) {
  185. value = (value * 10) + (text[_index].codeUnitAt(0) - 48);
  186. _index++;
  187. }
  188. if (_index == startIndex || _index - startIndex > 3 || value > 255) {
  189. return false;
  190. }
  191. groups++;
  192. if (groups < 4 && _index < text.length && text[_index] == '.') {
  193. _index++;
  194. }
  195. }
  196. return groups == 4;
  197. }
  198. // Returns true if the first letter of the string is
  199. // a,b,c,d,e,f,A,B,C,D,E,F,1,2,3,4,5,6,7,8,9,0
  200. // otherwise, the function returns false
  201. static bool _isHexDigit(String str) {
  202. final c = str.codeUnitAt(0);
  203. return (c >= 65 && c <= 70) ||
  204. (c >= 97 && c <= 102) ||
  205. (c >= 48 && c <= 57);
  206. }
  207. // This needs to handle the following forms:
  208. //
  209. // IPv6-addr = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp
  210. // IPv6-hex = 1*4HEXDIG
  211. // IPv6-full = IPv6-hex 7(":" IPv6-hex)
  212. // IPv6-comp = [IPv6-hex *5(":" IPv6-hex)] "::" [IPv6-hex *5(":" IPv6-hex)]
  213. // ; The "::" represents at least 2 16-bit groups of zeros
  214. // ; No more than 6 groups in addition to the "::" may be
  215. // ; present
  216. // IPv6v4-full = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal
  217. // IPv6v4-comp = [IPv6-hex *3(":" IPv6-hex)] "::"
  218. // [IPv6-hex *3(":" IPv6-hex) ":"] IPv4-address-literal
  219. // ; The "::" represents at least 2 16-bit groups of zeros
  220. // ; No more than 4 groups in addition to the "::" and
  221. // ; IPv4-address-literal may be present
  222. static bool _skipIPv6Literal(String text) {
  223. var compact = false;
  224. var colons = 0;
  225. while (_index < text.length) {
  226. var startIndex = _index;
  227. while (_index < text.length && _isHexDigit(text[_index])) {
  228. _index++;
  229. }
  230. if (_index >= text.length) {
  231. break;
  232. }
  233. if (_index > startIndex && colons > 2 && text[_index] == '.') {
  234. // IPv6v4
  235. _index = startIndex;
  236. if (!_skipIPv4Literal(text)) {
  237. return false;
  238. }
  239. return compact ? colons < 6 : colons == 6;
  240. }
  241. var count = _index - startIndex;
  242. if (count > 4) {
  243. return false;
  244. }
  245. if (text[_index] != ':') {
  246. break;
  247. }
  248. startIndex = _index;
  249. while (_index < text.length && text[_index] == ':') {
  250. _index++;
  251. }
  252. count = _index - startIndex;
  253. if (count > 2) {
  254. return false;
  255. }
  256. if (count == 2) {
  257. if (compact) {
  258. return false;
  259. }
  260. compact = true;
  261. colons += 2;
  262. } else {
  263. colons++;
  264. }
  265. }
  266. if (colons < 2) {
  267. return false;
  268. }
  269. return compact ? colons < 7 : colons == 7;
  270. }
  271. /// Validate the specified email address.
  272. ///
  273. /// If [allowTopLevelDomains] is `true`, then the validator will
  274. /// allow addresses with top-level domains like `email@example`.
  275. ///
  276. /// If [allowInternational] is `true`, then the validator
  277. /// will use the newer International Email standards for validating
  278. /// the email address.
  279. static bool validate(String email,
  280. [bool allowTopLevelDomains = false, bool allowInternational = true]) {
  281. _index = 0;
  282. if (email.isEmpty || email.length >= 255) {
  283. return false;
  284. }
  285. // Local-part = Dot-string / Quoted-string
  286. // ; MAY be case-sensitive
  287. //
  288. // Dot-string = Atom *("." Atom)
  289. //
  290. // Quoted-string = DQUOTE *qcontent DQUOTE
  291. if (email[_index] == '"') {
  292. if (!_skipQuoted(email, allowInternational) || _index >= email.length) {
  293. return false;
  294. }
  295. } else {
  296. if (!_skipAtom(email, allowInternational) || _index >= email.length) {
  297. return false;
  298. }
  299. while (email[_index] == '.') {
  300. _index++;
  301. if (_index >= email.length) {
  302. return false;
  303. }
  304. if (!_skipAtom(email, allowInternational)) {
  305. return false;
  306. }
  307. if (_index >= email.length) {
  308. return false;
  309. }
  310. }
  311. }
  312. if (_index + 1 >= email.length || _index > 64 || email[_index++] != '@') {
  313. return false;
  314. }
  315. if (email[_index] != '[') {
  316. // domain
  317. if (!_skipDomain(email, allowTopLevelDomains, allowInternational)) {
  318. return false;
  319. }
  320. return _index == email.length;
  321. }
  322. // address literal
  323. _index++;
  324. // we need at least 8 more characters
  325. if (_index + 8 >= email.length) {
  326. return false;
  327. }
  328. final ipv6 = email.substring(_index - 1).toLowerCase();
  329. if (ipv6.contains('ipv6:')) {
  330. _index += 'IPv6:'.length;
  331. if (!_skipIPv6Literal(email)) {
  332. return false;
  333. }
  334. } else {
  335. if (!_skipIPv4Literal(email)) {
  336. return false;
  337. }
  338. }
  339. if (_index >= email.length || email[_index++] != ']') {
  340. return false;
  341. }
  342. return _index == email.length;
  343. }
  344. }
  345. class PhoneNumberValidator {
  346. String? validateMobile(String value) {
  347. // Indian Mobile number are of 10 digit only
  348. if (value.length != 10) {
  349. return 'Mobile Number must be of 10 digit';
  350. } else {
  351. return null;
  352. }
  353. }
  354. }