diff --git a/CHANGELOG.md b/CHANGELOG.md index 437c375..63943d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +### **APR-14-2025** + +#### v1.0.1 - SRE fixes +#### workflow fix + ### **FEB-17-2025** #### v1.0.0 - v3 migration null support added diff --git a/lib/contentstack.dart b/lib/contentstack.dart index 760780f..9b89518 100644 --- a/lib/contentstack.dart +++ b/lib/contentstack.dart @@ -8,6 +8,8 @@ export 'src/asset.dart'; export 'src/contenttype.dart'; export 'src/contenttype_query.dart'; export 'src/entry.dart'; +export 'src/enums/include.dart'; +export 'src/enums/include_type.dart'; export 'src/error/error.dart'; export 'src/image_transform.dart'; export 'src/models/assetmodel.dart'; diff --git a/lib/src/base_query.dart b/lib/src/base_query.dart index 29086a6..0fe9efa 100644 --- a/lib/src/base_query.dart +++ b/lib/src/base_query.dart @@ -156,8 +156,8 @@ class BaseQuery { } void where(String fieldUid, QueryOperation queryOperation) { - if (fieldUid.isNotEmpty) { - switch(queryOperation.operationType) { + if(_isValidFieldUid(fieldUid)) { + switch (queryOperation.operationType) { case QueryOperationType.Equals: parameter[fieldUid] = queryOperation.value; break; @@ -191,4 +191,10 @@ class BaseQuery { } } } + + bool _isValidFieldUid(String fieldUid) { + final validFieldUidPattern = RegExp(r'^[a-zA-Z0-9-_.]+$'); + return validFieldUidPattern.hasMatch(fieldUid); + } + } diff --git a/lib/src/entry_queryable.dart b/lib/src/entry_queryable.dart index afee9c3..ce99575 100644 --- a/lib/src/entry_queryable.dart +++ b/lib/src/entry_queryable.dart @@ -1,4 +1,4 @@ -// ignore_for_file: lines_longer_than_80_chars +// ignore_for_file: lines_longer_than_80_chars, unnecessary_lambdas import 'package:contentstack/constant.dart'; import 'package:contentstack/src/enums/include.dart'; @@ -8,6 +8,11 @@ import 'package:contentstack/src/enums/include_type.dart'; class EntryQueryable { Map parameter = {}; + //sanitize + String sanitizeInput(String input) { + return input.replaceAll(RegExp(r'[^a-zA-Z0-9-_.]'), ''); +} + /// /// This method adds key and value to an Entry. /// [key] The key as string which needs to be added to an Entry @@ -43,10 +48,7 @@ class EntryQueryable { /// void except(List fieldUid) { if (fieldUid.isNotEmpty) { - final List referenceArray = []; - for (final item in fieldUid) { - referenceArray.add(item); - } + final List referenceArray = fieldUid.map((item) => sanitizeInput(item)).toList(); parameter['except[BASE][]'] = referenceArray.toString(); } } @@ -171,15 +173,15 @@ class EntryQueryable { case IncludeType.None: if (referenceFieldUid.runtimeType == List) { for (var uid in referenceFieldUid) { - referenceArray.add(uid); + referenceArray.add(sanitizeInput(uid)); } } else if (referenceFieldUid.runtimeType == String) { - referenceArray.add(referenceFieldUid); + referenceArray.add(sanitizeInput(referenceFieldUid)); } if (includeReferenceField.fieldUidList.isNotEmpty) { for (final item in includeReferenceField.fieldUidList) { - referenceArray.add(item); + referenceArray.add(sanitizeInput(item)); } } parameter['include[]'] = referenceArray.toString(); @@ -188,7 +190,7 @@ class EntryQueryable { final Map referenceOnlyParam = {}; if (includeReferenceField.fieldUidList.isNotEmpty) { for (final item in includeReferenceField.fieldUidList) { - referenceArray.add(item); + referenceArray.add(sanitizeInput(item)); } } referenceOnlyParam[referenceFieldUid] = referenceArray; @@ -199,7 +201,7 @@ class EntryQueryable { final Map referenceOnlyParam = {}; if (includeReferenceField.fieldUidList.isNotEmpty) { for (final item in includeReferenceField.fieldUidList) { - referenceArray.add(item); + referenceArray.add(sanitizeInput(item)); } } referenceOnlyParam[referenceFieldUid] = referenceArray; @@ -262,10 +264,7 @@ class EntryQueryable { /// void only(List fieldUid) { if (fieldUid.isNotEmpty) { - final List referenceArray = []; - for (final item in fieldUid) { - referenceArray.add(item); - } + final List referenceArray = fieldUid.map((item) => sanitizeInput(item)).toList(); parameter['only[BASE][]'] = referenceArray.toString(); } } diff --git a/lib/src/query_params.dart b/lib/src/query_params.dart index ca817e5..10898ea 100644 --- a/lib/src/query_params.dart +++ b/lib/src/query_params.dart @@ -5,7 +5,8 @@ class URLQueryParams { // Appends a parameter to the query with received key. void append(String key, dynamic value) { if (value != null && value.toString().isNotEmpty) { - _values[key] = Uri.encodeQueryComponent(value.toString()); + final sanitizedValue = _sanitizeInput(value.toString()); + _values[key] = Uri.encodeQueryComponent(sanitizedValue); } } @@ -18,20 +19,22 @@ class URLQueryParams { // * param1=value1¶m2=value2 @override String toString() { - String response = ''; - _values.forEach((key, value) { - response += '$key=$value&'; - }); - return response.substring(0, response.isEmpty ? 0 : response.length - 1); + return _values.entries + .map((entry) => '${Uri.encodeQueryComponent(entry.key)}=${entry.value}') + .join('&'); } - String toUrl(String urls) { - String updatedUrl; - if (urls.isNotEmpty && urls.endsWith('/')) { - updatedUrl = urls.substring(0, urls.length - 1); - } else { - updatedUrl = urls; - } - return '$updatedUrl?${toString()}'; + String toUrl(String url) { + if (url.isEmpty) throw ArgumentError('URL cannot be empty'); + + final Uri parsedUri = Uri.parse(url); + final String normalizedUrl = parsedUri.normalizePath().toString(); + + return Uri.parse('$normalizedUrl?${toString()}').toString(); + } + + String _sanitizeInput(String input) { + final pattern = RegExp(r'[<>\"\;(){}]'); + return input.replaceAll(pattern, ''); } } diff --git a/lib/src/stack.dart b/lib/src/stack.dart index 5f9d213..6d0c3e1 100644 --- a/lib/src/stack.dart +++ b/lib/src/stack.dart @@ -463,8 +463,6 @@ class Stack { 'authorization': headers!['authorization']!, 'api_key': headers!['api_key']!, }; - - print('Request URL: $_url'); await http.get(Uri.parse(_url), headers: _headers).then((response) { final Map bodyJson = json.decode(utf8.decode(response.bodyBytes)); print(bodyJson); diff --git a/pubspec.yaml b/pubspec.yaml index 1099cea..17fb2db 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: contentstack description: Contentstack is a headless CMS with an API-first approach that puts content at the centre. -version: 1.0.0 +version: 1.0.1 homepage: https://www.contentstack.com documentation: https://www.contentstack.com/docs/developers/apis/content-delivery-api diff --git a/test/query_test.dart b/test/query_test.dart index 79dd189..4f07853 100644 --- a/test/query_test.dart +++ b/test/query_test.dart @@ -393,7 +393,6 @@ void main() { query.operator(QueryOperator(QueryOperatorType.Or, listOfQuery)); await query.find().then((response) { final completeUrl = query.getQueryUrl()['query']; - //(response.toString()); expect('{\"\$or\":[{\"title\":\"Room 13\"},{\"number\":20}]}', completeUrl); }).catchError((onError) { diff --git a/test/stack_test.dart b/test/stack_test.dart index 915b686..c96a13b 100644 --- a/test/stack_test.dart +++ b/test/stack_test.dart @@ -143,7 +143,7 @@ void main() { group('testcase for URLQueryParams', () { test('test query_params', () { final params = URLQueryParams()..append('key', 'value'); - final url = params.toUrl('cdn.contentstack.io/'); + final url = params.toUrl('cdn.contentstack.io'); expect('cdn.contentstack.io?key=value', url); });