Skip to content

Commit 8045b64

Browse files
committed
♻️ Enhance string validation methods and add error handling for edge cases
1 parent 3fe0992 commit 8045b64

2 files changed

Lines changed: 78 additions & 14 deletions

File tree

lib/src/nullable_string_extensions.dart

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ extension MiscExtensionsNullable on String? {
102102
if (this.isBlank) {
103103
return 0;
104104
}
105+
if (wordsPerMinute <= 0) {
106+
throw ArgumentError('wordsPerMinute must be greater than 0');
107+
}
105108
var words = this!.trim().split(RegExp(r'(\s+)'));
106109
var magicalNumber = words.length / wordsPerMinute;
107110
return (magicalNumber * 100).toInt();
@@ -311,10 +314,16 @@ extension MiscExtensionsNullable on String? {
311314
if (this.isBlank) {
312315
return false;
313316
}
314-
this!.substring(0, 1);
315-
var regex = RegExp(
316-
r'(([0-9a-fA-F]{1,4}:){7,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}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))');
317-
return regex.hasMatch(this!);
317+
318+
// Simple IPv6 validation using Uri.tryParse for security
319+
try {
320+
var uri = Uri.tryParse('http://[${this!}]');
321+
return uri != null && uri.host.isNotEmpty;
322+
} catch (e) {
323+
// Additional simple regex check for basic IPv6 format
324+
var regex = RegExp(r'^[0-9a-fA-F:]+$');
325+
return regex.hasMatch(this!) && this!.contains(':') && this!.length <= 39;
326+
}
318327
}
319328

320329
/// Checks whether the `String` is a valid URL.
@@ -332,9 +341,23 @@ extension MiscExtensionsNullable on String? {
332341
if (this.isBlank) {
333342
return false;
334343
}
335-
var regex = RegExp(
336-
r'[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)');
337-
return regex.hasMatch(this!);
344+
345+
// Use Uri.tryParse for secure URL validation
346+
try {
347+
var uri = Uri.tryParse(this!);
348+
if (uri == null) return false;
349+
350+
// Check for valid schemes and prevent dangerous ones
351+
var validSchemes = ['http', 'https', 'ftp', 'ftps'];
352+
if (!validSchemes.contains(uri.scheme.toLowerCase())) {
353+
return false;
354+
}
355+
356+
// Must have a valid host
357+
return uri.hasAuthority && uri.host.isNotEmpty;
358+
} catch (e) {
359+
return false;
360+
}
338361
}
339362

340363
/// Checks whether the `String` is a valid `DateTime`:
@@ -545,6 +568,9 @@ extension MiscExtensionsNullable on String? {
545568
// ignore: omit_local_variable_types
546569
List<Map<String, int>> occurrences = [];
547570
var letters = this!.split('')..sort();
571+
if (letters.isEmpty) {
572+
return [];
573+
}
548574
var checkingLetter = letters[0];
549575
var count = 0;
550576
for (var i = 0; i < letters.length; i++) {
@@ -593,6 +619,9 @@ extension MiscExtensionsNullable on String? {
593619
}
594620
var occurrences = <String, int>{};
595621
var letters = this!.split('')..sort();
622+
if (letters.isEmpty) {
623+
return this;
624+
}
596625
var checkingLetter = letters[0];
597626
var count = 0;
598627

@@ -761,6 +790,9 @@ extension MiscExtensionsNullable on String? {
761790
}
762791

763792
var words = this!.trim().split(RegExp(r'(\s+)'));
793+
if (words.isEmpty) {
794+
return this;
795+
}
764796
var result = words[0].toLowerCase();
765797
for (var i = 1; i < words.length; i++) {
766798
result += words[i].substring(0, 1).toUpperCase() +

lib/src/string_extensions.dart

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ extension MiscExtensionsNonNullable on String {
101101
if (this.isBlank) {
102102
return 0;
103103
}
104+
if (wordsPerMinute <= 0) {
105+
throw ArgumentError('wordsPerMinute must be greater than 0');
106+
}
104107
var words = this.trim().split(RegExp(r'(\s+)'));
105108
var magicalNumber = words.length / wordsPerMinute;
106109
return (magicalNumber * 100).toInt();
@@ -280,10 +283,16 @@ extension MiscExtensionsNonNullable on String {
280283
if (this.isBlank) {
281284
return false;
282285
}
283-
this.substring(0, 1);
284-
var regex = RegExp(
285-
r'(([0-9a-fA-F]{1,4}:){7,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}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))');
286-
return regex.hasMatch(this);
286+
287+
// Simple IPv6 validation using Uri.tryParse for security
288+
try {
289+
var uri = Uri.tryParse('http://[$this]');
290+
return uri != null && uri.host.isNotEmpty;
291+
} catch (e) {
292+
// Additional simple regex check for basic IPv6 format
293+
var regex = RegExp(r'^[0-9a-fA-F:]+$');
294+
return regex.hasMatch(this) && this.contains(':') && this.length <= 39;
295+
}
287296
}
288297

289298
/// Checks whether the `String` is a valid URL.
@@ -301,9 +310,23 @@ extension MiscExtensionsNonNullable on String {
301310
if (this.isBlank) {
302311
return false;
303312
}
304-
var regex = RegExp(
305-
r'[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)');
306-
return regex.hasMatch(this);
313+
314+
// Use Uri.tryParse for secure URL validation
315+
try {
316+
var uri = Uri.tryParse(this);
317+
if (uri == null) return false;
318+
319+
// Check for valid schemes and prevent dangerous ones
320+
var validSchemes = ['http', 'https', 'ftp', 'ftps'];
321+
if (!validSchemes.contains(uri.scheme.toLowerCase())) {
322+
return false;
323+
}
324+
325+
// Must have a valid host
326+
return uri.hasAuthority && uri.host.isNotEmpty;
327+
} catch (e) {
328+
return false;
329+
}
307330
}
308331

309332
/// Checks whether the `String` is a valid `DateTime`:
@@ -514,6 +537,9 @@ extension MiscExtensionsNonNullable on String {
514537
// ignore: omit_local_variable_types
515538
List<Map<String, int>> occurrences = [];
516539
var letters = this.split('')..sort();
540+
if (letters.isEmpty) {
541+
return [];
542+
}
517543
var checkingLetter = letters[0];
518544
var count = 0;
519545
for (var i = 0; i < letters.length; i++) {
@@ -562,6 +588,9 @@ extension MiscExtensionsNonNullable on String {
562588
}
563589
var occurrences = <String, int>{};
564590
var letters = this.split('')..sort();
591+
if (letters.isEmpty) {
592+
return this;
593+
}
565594
var checkingLetter = letters[0];
566595
var count = 0;
567596

@@ -730,6 +759,9 @@ extension MiscExtensionsNonNullable on String {
730759
}
731760

732761
var words = this.trim().split(RegExp(r'(\s+)'));
762+
if (words.isEmpty) {
763+
return this;
764+
}
733765
var result = words[0].toLowerCase();
734766
for (var i = 1; i < words.length; i++) {
735767
result += words[i].substring(0, 1).toUpperCase() +

0 commit comments

Comments
 (0)