From 0c3ef18b9c29ac10c832a99f1cff45ff1bf2a614 Mon Sep 17 00:00:00 2001 From: suragch Date: Thu, 23 Feb 2023 16:19:40 +0800 Subject: [PATCH] version 1.0.0 --- CHANGELOG.md | 6 ++ analysis_options.yaml | 5 + lib/src/helpers.dart | 15 ++- lib/src/sanitizer.dart | 19 ++-- lib/src/validator.dart | 205 ++++++++++++++++++++------------------- pubspec.yaml | 6 +- test/validator_test.dart | 12 +-- 7 files changed, 136 insertions(+), 132 deletions(-) create mode 100644 analysis_options.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 189b17f..eeb3922 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changes +## 1.0.0 + +- Seems to have been stable for a long time so bumping to 1.0.0. +- Fixed analyzer warnings. +- Added type safety. + ## 0.3.0 - Null safety stable bump diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..8f7a725 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,5 @@ +include: package:lints/recommended.yaml + +analyzer: + strong-mode: + implicit-dynamic: false \ No newline at end of file diff --git a/lib/src/helpers.dart b/lib/src/helpers.dart index 1ff7d60..c6e8b2b 100644 --- a/lib/src/helpers.dart +++ b/lib/src/helpers.dart @@ -1,15 +1,14 @@ // Helper functions for validator and sanitizer. -shift(List l) { - if (l.isNotEmpty) { - var first = l.first; - l.removeAt(0); - return first; - } - return null; +String? shift(List elements) { + if (elements.isEmpty) return null; + return elements.removeAt(0); } -Map merge(Map? obj, Map defaults) { +Map merge( + Map? obj, + Map defaults, +) { if (obj == null) { return defaults; } diff --git a/lib/src/sanitizer.dart b/lib/src/sanitizer.dart index 6c3b8ee..1e74e50 100644 --- a/lib/src/sanitizer.dart +++ b/lib/src/sanitizer.dart @@ -1,10 +1,10 @@ import 'helpers.dart'; import 'validator.dart'; -Map _default_normalize_email_options = {'lowercase': true}; +Map _defaultNormalizeEmailOptions = {'lowercase': true}; /// convert the input to a string -String toString(input) { +String toString(Object? input) { if (input == null || (input is List && input.isEmpty)) { input = ''; } @@ -82,7 +82,7 @@ String rtrim(String str, [String? chars]) { /// The characters are used in a RegExp and so you will need to escape /// some chars. String whitelist(String str, String chars) { - return str.replaceAll(RegExp('[^' + chars + ']+'), ''); + return str.replaceAll(RegExp('[^$chars]+'), ''); } /// remove characters that appear in the blacklist. @@ -90,17 +90,16 @@ String whitelist(String str, String chars) { /// The characters are used in a RegExp and so you will need to escape /// some chars. String blacklist(String str, String chars) { - return str.replaceAll(RegExp('[' + chars + ']+'), ''); + return str.replaceAll(RegExp('[$chars]+'), ''); } /// remove characters with a numerical value < 32 and 127. /// /// If `keep_new_lines` is `true`, newline characters are preserved /// `(\n and \r, hex 0xA and 0xD)`. -String stripLow(String str, [bool keep_new_lines = false]) { - String chars = keep_new_lines == true - ? '\x00-\x09\x0B\x0C\x0E-\x1F\x7F' - : '\x00-\x1F\x7F'; +String stripLow(String str, [bool keepNewLines = false]) { + String chars = + keepNewLines == true ? '\x00-\x09\x0B\x0C\x0E-\x1F\x7F' : '\x00-\x1F\x7F'; return blacklist(str, chars); } @@ -126,7 +125,7 @@ String escape(String str) { /// tags (e.g. `some.one+tag@gmail.com` becomes `someone@gmail.com`) and all /// `@googlemail.com` addresses are normalized to `@gmail.com`. String normalizeEmail(String email, [Map? options]) { - options = merge(options, _default_normalize_email_options); + options = merge(options, _defaultNormalizeEmailOptions); if (isEmail(email) == false) { return ''; } @@ -142,7 +141,7 @@ String normalizeEmail(String email, [Map? options]) { if (options['lowercase'] == false) { parts[0] = parts[0].toLowerCase(); } - parts[0] = parts[0].replaceAll('\.', '').split('+')[0]; + parts[0] = parts[0].replaceAll('.', '').split('+')[0]; parts[1] = 'gmail.com'; } return parts.join('@'); diff --git a/lib/src/validator.dart b/lib/src/validator.dart index 1a3ed90..8660c6d 100644 --- a/lib/src/validator.dart +++ b/lib/src/validator.dart @@ -18,7 +18,7 @@ RegExp _int = RegExp(r'^(?:-?(?:0|[1-9][0-9]*))$'); RegExp _float = RegExp(r'^(?:-?(?:[0-9]+))?(?:\.[0-9]*)?(?:[eE][\+\-]?(?:[0-9]+))?$'); RegExp _hexadecimal = RegExp(r'^[0-9a-fA-F]+$'); -RegExp _hexcolor = RegExp(r'^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$'); +RegExp _hexColor = RegExp(r'^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$'); RegExp _base64 = RegExp( r'^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$'); @@ -29,7 +29,7 @@ RegExp _creditCard = RegExp( RegExp _isbn10Maybe = RegExp(r'^(?:[0-9]{9}X|[0-9]{10})$'); RegExp _isbn13Maybe = RegExp(r'^(?:[0-9]{13})$'); -Map _uuid = { +Map _uuid = { '3': RegExp( r'^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$'), '4': RegExp( @@ -48,17 +48,17 @@ RegExp _halfWidth = RegExp(r'[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]'); /// check if the string matches the comparison -bool equals(String str, comparison) { +bool equals(String str, Object? comparison) { return str == comparison.toString(); } /// check if the string contains the substring -bool contains(String str, substring) { +bool contains(String str, String substring) { return str.contains(substring); } /// check if string matches the pattern. -bool matches(String str, pattern) { +bool matches(String str, String pattern) { RegExp re = RegExp(pattern); return re.hasMatch(str); } @@ -73,39 +73,43 @@ bool isEmail(String str) { /// `options` is a `Map` which defaults to /// `{ 'protocols': ['http','https','ftp'], 'require_tld': true, /// 'require_protocol': false, 'allow_underscores': false }`. -bool isURL(String str, [Map? options]) { - if (str.isEmpty || str.length > 2083 || str.indexOf('mailto:') == 0) { +bool isURL(String? input, [Map? options]) { + var str = input; + if (str == null || + str.isEmpty || + str.length > 2083 || + str.indexOf('mailto:') == 0) { return false; } - final default_url_options = { + final defaultUrlOptions = { 'protocols': ['http', 'https', 'ftp'], 'require_tld': true, 'require_protocol': false, 'allow_underscores': false, }; - options = merge(options, default_url_options); - - var protocol, - user, - pass, - auth, - host, - hostname, - port, - port_str, - path, - query, - hash, - split; + options = merge(options, defaultUrlOptions); + + // String? protocol; + // String user; + // String pass; + // // String auth; + // String host; + // String hostname; + // String port; + // String portStr; + // String path; + // String query; + // String hash; + // List split; // check protocol - split = str.split('://'); + var split = str.split('://'); if (split.length > 1) { - protocol = shift(split); + final protocol = shift(split); final protocols = options['protocols'] as List; - if (protocols.indexOf(protocol) == -1) { + if (!protocols.contains(protocol)) { return false; } } else if (options['require_protocol'] == true) { @@ -116,38 +120,39 @@ bool isURL(String str, [Map? options]) { // check hash split = str.split('#'); str = shift(split); - hash = split.join('#'); - if (hash != null && hash != "" && RegExp(r'\s').hasMatch(hash)) { + final hash = split.join('#'); + if (hash.isNotEmpty && RegExp(r'\s').hasMatch(hash)) { return false; } // check query params - split = str.split('?'); + split = str?.split('?') ?? []; str = shift(split); - query = split.join('?'); - if (query != null && query != "" && RegExp(r'\s').hasMatch(query)) { + final query = split.join('?'); + if (query != "" && RegExp(r'\s').hasMatch(query)) { return false; } // check path - split = str.split('/'); + split = str?.split('/') ?? []; str = shift(split); - path = split.join('/'); - if (path != null && path != "" && RegExp(r'\s').hasMatch(path)) { + final path = split.join('/'); + if (path != "" && RegExp(r'\s').hasMatch(path)) { return false; } // check auth type urls - split = str.split('@'); + split = str?.split('@') ?? []; if (split.length > 1) { - auth = shift(split); - if (auth.indexOf(':') >= 0) { - auth = auth.split(':'); - user = shift(auth); - if (!RegExp(r'^\S+$').hasMatch(user)) { + final auth = shift(split); + if (auth != null && auth.contains(':')) { + // final auth = auth.split(':'); + final parts = auth.split(':'); + final user = shift(parts); + if (user == null || !RegExp(r'^\S+$').hasMatch(user)) { return false; } - pass = auth.join(':'); + final pass = parts.join(':'); if (!RegExp(r'^\S*$').hasMatch(pass)) { return false; } @@ -155,22 +160,22 @@ bool isURL(String str, [Map? options]) { } // check hostname - hostname = split.join('@'); + final hostname = split.join('@'); split = hostname.split(':'); - host = shift(split); - if (split.length > 0) { - port_str = split.join(':'); - try { - port = int.parse(port_str, radix: 10); - } catch (e) { - return false; - } - if (!RegExp(r'^[0-9]+$').hasMatch(port_str) || port <= 0 || port > 65535) { + final host = shift(split); + if (split.isNotEmpty) { + final portStr = split.join(':'); + final port = int.tryParse(portStr, radix: 10); + if (!RegExp(r'^[0-9]+$').hasMatch(portStr) || + port == null || + port <= 0 || + port > 65535) { return false; } } - if (!isIP(host) && !isFQDN(host, options) && host != 'localhost') { + if (host == null || + !isIP(host) && !isFQDN(host, options) && host != 'localhost') { return false; } @@ -180,7 +185,8 @@ bool isURL(String str, [Map? options]) { /// check if the string is an IP (version 4 or 6) /// /// `version` is a String or an `int`. -bool isIP(String str, [version]) { +bool isIP(String str, [Object? version]) { + assert(version == null || version is String || version is int); version = version.toString(); if (version == 'null') { return isIP(str, 4) || isIP(str, 6); @@ -199,13 +205,10 @@ bool isIP(String str, [version]) { /// /// `options` is a `Map` which defaults to `{ 'require_tld': true, 'allow_underscores': false }`. bool isFQDN(String str, [Map? options]) { - final default_fqdn_options = { - 'require_tld': true, - 'allow_underscores': false - }; + final defaultFqdnOptions = {'require_tld': true, 'allow_underscores': false}; - options = merge(options, default_fqdn_options); - List parts = str.split('.'); + options = merge(options, defaultFqdnOptions); + final parts = str.split('.'); if (options['require_tld'] as bool) { var tld = parts.removeLast(); if (parts.isEmpty || !RegExp(r'^[a-z]{2,}$').hasMatch(tld)) { @@ -213,10 +216,9 @@ bool isFQDN(String str, [Map? options]) { } } - for (var part, i = 0; i < parts.length; i++) { - part = parts[i]; + for (final part in parts) { if (options['allow_underscores'] as bool) { - if (part.indexOf('__') >= 0) { + if (part.contains('__')) { return false; } } @@ -225,7 +227,7 @@ bool isFQDN(String str, [Map? options]) { } if (part[0] == '-' || part[part.length - 1] == '-' || - part.indexOf('---') >= 0) { + part.contains('---')) { return false; } } @@ -269,7 +271,7 @@ bool isHexadecimal(String str) { /// check if the string is a hexadecimal color bool isHexColor(String str) { - return _hexcolor.hasMatch(str); + return _hexColor.hasMatch(str); } /// check if the string is lowercase @@ -285,9 +287,19 @@ bool isUppercase(String str) { /// check if the string is a number that's divisible by another /// /// [n] is a String or an int. -bool isDivisibleBy(String str, n) { +bool isDivisibleBy(String str, Object n) { + assert(n is String || n is int); + final int? number; + if (n is int) { + number = n; + } else if (n is String) { + number = int.tryParse(n); + } else { + return false; + } + if (number == null) return false; try { - return double.parse(str) % int.parse(n) == 0; + return double.parse(str) % number == 0; } catch (e) { return false; } @@ -309,7 +321,7 @@ bool isByteLength(String str, int min, [int? max]) { } /// check if the string is a UUID (version 3, 4 or 5). -bool isUUID(String str, [version]) { +bool isUUID(String str, [Object? version]) { if (version == null) { version = 'all'; } else { @@ -322,69 +334,58 @@ bool isUUID(String str, [version]) { /// check if the string is a date bool isDate(String str) { - try { - DateTime.parse(str); - return true; - } catch (e) { - return false; - } + return DateTime.tryParse(str) != null; } /// check if the string is a date that's after the specified date /// /// If `date` is not passed, it defaults to now. -bool isAfter(String str, [date]) { +bool isAfter(String str, [String? date]) { + DateTime referenceDate; if (date == null) { - date = DateTime.now(); + referenceDate = DateTime.now(); } else if (isDate(date)) { - date = DateTime.parse(date); + referenceDate = DateTime.parse(date); } else { return false; } - DateTime str_date; - try { - str_date = DateTime.parse(str); - } catch (e) { - return false; - } + final strDate = DateTime.tryParse(str); + if (strDate == null) return false; - return str_date.isAfter(date); + return strDate.isAfter(referenceDate); } /// check if the string is a date that's before the specified date /// /// If `date` is not passed, it defaults to now. -bool isBefore(String str, [date]) { +bool isBefore(String str, [String? date]) { + DateTime referenceDate; if (date == null) { - date = DateTime.now(); + referenceDate = DateTime.now(); } else if (isDate(date)) { - date = DateTime.parse(date); + referenceDate = DateTime.parse(date); } else { return false; } - DateTime str_date; - try { - str_date = DateTime.parse(str); - } catch (e) { - return false; - } + final strDate = DateTime.tryParse(str); + if (strDate == null) return false; - return str_date.isBefore(date); + return strDate.isBefore(referenceDate); } /// check if the string is in an array of allowed values -bool isIn(String str, values) { - if (values == null || values.length == 0) { - return false; +bool isIn(String str, Object? values) { + if (values == null) return false; + if (values is String) { + return values.contains(str); } - - if (values is List) { - values = values.map((e) => e.toString()).toList(); + if (values is! Iterable) return false; + for (Object? value in values) { + if (value.toString() == str) return true; } - - return values.indexOf(str) >= 0; + return false; } /// check if the string is a credit card @@ -420,7 +421,7 @@ bool isCreditCard(String str) { } /// check if the string is an ISBN (version 10 or 13) -bool isISBN(String str, [version]) { +bool isISBN(String str, [Object? version]) { if (version == null) { return isISBN(str, '10') || isISBN(str, '13'); } @@ -458,7 +459,7 @@ bool isISBN(String str, [version]) { } /// check if the string is valid JSON -bool isJson(str) { +bool isJson(String str) { try { json.decode(str); } catch (e) { diff --git a/pubspec.yaml b/pubspec.yaml index 72210c3..17a2d62 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: string_validator -version: 0.3.0 +version: 1.0.0 description: Dart library for validating and sanitizing strings, especially those from user input. homepage: https://github.com/suragch/string_validator @@ -8,5 +8,5 @@ environment: sdk: '>=2.12.0 <3.0.0' dev_dependencies: - pedantic: ^1.11.0 - test: ^1.16.8 + lints: ^2.0.0 + test: ^1.23.1 diff --git a/test/validator_test.dart b/test/validator_test.dart index 7d40e0b..35b516d 100644 --- a/test/validator_test.dart +++ b/test/validator_test.dart @@ -98,8 +98,7 @@ void main() { expect(v.isURL('http://www.foo---bar.com/'), equals(false)); expect(v.isURL('http://www.foo_bar.com/'), equals(false)); expect(v.isURL(''), equals(false)); - expect(v.isURL('http://foobar.com/' + ('f' * 2083)), - equals(false)); + expect(v.isURL('http://foobar.com/${'f' * 2083}'), equals(false)); expect(v.isURL('http://*.foo.com'), equals(false)); expect(v.isURL('*.foo.com'), equals(false)); expect(v.isURL('!.foo.com'), equals(false)); @@ -169,13 +168,8 @@ void main() { equals(true)); expect(v.isBase64('U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw=='), equals(true)); expect( - v.isBase64('MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMPNS1Ufof9EW/M98FNw' + - 'UAKrwflsqVxaxQjBQnHQmiI7Vac40t8x7pIb8gLGV6wL7sBTJiPovJ0V7y7oc0Ye' + - 'rhKh0Rm4skP2z/jHwwZICgGzBvA0rH8xlhUiTvcwDCJ0kc+fh35hNt8srZQM4619' + - 'FTgB66Xmp4EtVyhpQV+t02g6NzK72oZI0vnAvqhpkxLeLiMCyrI416wHm5Tkukhx' + - 'QmcL2a6hNOyu0ixX/x2kSFXApEnVrJ+/IxGyfyw8kf4N2IZpW5nEP847lpfj0SZZ' + - 'Fwrd1mnfnDbYohX2zRptLy2ZUn06Qo9pkG5ntvFEPo9bfZeULtjYzIl6K8gJ2uGZ' + - 'HQIDAQAB'), + v.isBase64( + 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMPNS1Ufof9EW/M98FNwUAKrwflsqVxaxQjBQnHQmiI7Vac40t8x7pIb8gLGV6wL7sBTJiPovJ0V7y7oc0YerhKh0Rm4skP2z/jHwwZICgGzBvA0rH8xlhUiTvcwDCJ0kc+fh35hNt8srZQM4619FTgB66Xmp4EtVyhpQV+t02g6NzK72oZI0vnAvqhpkxLeLiMCyrI416wHm5TkukhxQmcL2a6hNOyu0ixX/x2kSFXApEnVrJ+/IxGyfyw8kf4N2IZpW5nEP847lpfj0SZZFwrd1mnfnDbYohX2zRptLy2ZUn06Qo9pkG5ntvFEPo9bfZeULtjYzIl6K8gJ2uGZHQIDAQAB'), equals(true)); // invalid expect(v.isBase64('abc!'), equals(false));