From 0942ba6164377c54170aac10fa1d163549525ff5 Mon Sep 17 00:00:00 2001 From: Jesse Vickery Date: Thu, 6 Feb 2025 20:12:22 +0000 Subject: [PATCH 1/3] feat(views): ds dump filename; - Filename for ds dump endpoint. --- ckanext/datastore/blueprint.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ckanext/datastore/blueprint.py b/ckanext/datastore/blueprint.py index 2f4ff37961e..818f95c5f35 100644 --- a/ckanext/datastore/blueprint.py +++ b/ckanext/datastore/blueprint.py @@ -88,6 +88,7 @@ def dump_schema() -> Schema: u'language': [ignore_missing, unicode_only], u'fields': [exclude_id_from_ds_dump, ignore_missing, list_of_strings_or_string], # (canada fork only): exclude _id field from Blueprint dump u'sort': [default(u'_id'), list_of_strings_or_string], + 'filename': [ignore_missing, unicode_only] # (canada fork only): filename to save stream to } @@ -111,6 +112,9 @@ def dump(resource_id: str): limit = data.get('limit') options = {'bom': data['bom']} sort = data['sort'] + # (canada fork only): filename to save stream to + custom_filename = data.get('filename') + filename = custom_filename if custom_filename else resource_id search_params = { k: v for k, v in data.items() @@ -127,19 +131,19 @@ def dump(resource_id: str): if fmt == 'csv': content_disposition = 'attachment; filename="{name}.csv"'.format( - name=resource_id) + name=filename) # (canada fork only): filename to save stream to content_type = b'text/csv; charset=utf-8' elif fmt == 'tsv': content_disposition = 'attachment; filename="{name}.tsv"'.format( - name=resource_id) + name=filename) # (canada fork only): filename to save stream to content_type = b'text/tab-separated-values; charset=utf-8' elif fmt == 'json': content_disposition = 'attachment; filename="{name}.json"'.format( - name=resource_id) + name=filename) # (canada fork only): filename to save stream to content_type = b'application/json; charset=utf-8' elif fmt == 'xml': content_disposition = 'attachment; filename="{name}.xml"'.format( - name=resource_id) + name=filename) # (canada fork only): filename to save stream to content_type = b'text/xml; charset=utf-8' else: abort(404, _('Unsupported format')) From 4d8fc7fe05fe28e077bc149d677dfd850325b2a1 Mon Sep 17 00:00:00 2001 From: Jesse Vickery Date: Thu, 6 Feb 2025 20:28:13 +0000 Subject: [PATCH 2/3] feat(misc): changelog; - Added change log file. --- changes/190.canada.feature | 1 + ckanext/datastore/blueprint.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 changes/190.canada.feature diff --git a/changes/190.canada.feature b/changes/190.canada.feature new file mode 100644 index 00000000000..aa29eb66494 --- /dev/null +++ b/changes/190.canada.feature @@ -0,0 +1 @@ +Added a `filename` option to the DataStore Dump endpoint. diff --git a/ckanext/datastore/blueprint.py b/ckanext/datastore/blueprint.py index 818f95c5f35..9fe4418b4b6 100644 --- a/ckanext/datastore/blueprint.py +++ b/ckanext/datastore/blueprint.py @@ -113,8 +113,7 @@ def dump(resource_id: str): options = {'bom': data['bom']} sort = data['sort'] # (canada fork only): filename to save stream to - custom_filename = data.get('filename') - filename = custom_filename if custom_filename else resource_id + filename = data.get('filename', resource_id) search_params = { k: v for k, v in data.items() From 975ae947cbb08b950d38b2f5602aed3279c90ed2 Mon Sep 17 00:00:00 2001 From: Jesse Vickery Date: Mon, 10 Feb 2025 20:29:12 +0000 Subject: [PATCH 3/3] feat(views): filename validator; - Simple validator for filename in DS dump. --- ckanext/datastore/blueprint.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/ckanext/datastore/blueprint.py b/ckanext/datastore/blueprint.py index 9fe4418b4b6..3000e4a809c 100644 --- a/ckanext/datastore/blueprint.py +++ b/ckanext/datastore/blueprint.py @@ -28,6 +28,12 @@ json_writer, xml_writer, ) +# (canada fork only): filename to save stream to +import re + + +# (canada fork only): filename to save stream to +FILENAME_MATCH = re.compile('^[\w\-\.]+$') int_validator = get_validator(u'int_validator') boolean_validator = get_validator(u'boolean_validator') @@ -75,6 +81,18 @@ def exclude_id_from_ds_dump(key, data, errors, context): data[key] = value +# (canada fork only): filename to save stream to +def filename_safe(key, data, errors, context): + """ + Makes sure the passed filename is safe to stream back in the response. + """ + value = data.get(key) + + if not re.search(FILENAME_MATCH, value): + errors[key].append(_('Invalid characters in filename')) + raise StopOnError + + def dump_schema() -> Schema: return { u'offset': [default(0), int_validator], @@ -88,7 +106,7 @@ def dump_schema() -> Schema: u'language': [ignore_missing, unicode_only], u'fields': [exclude_id_from_ds_dump, ignore_missing, list_of_strings_or_string], # (canada fork only): exclude _id field from Blueprint dump u'sort': [default(u'_id'), list_of_strings_or_string], - 'filename': [ignore_missing, unicode_only] # (canada fork only): filename to save stream to + 'filename': [ignore_missing, unicode_only, filename_safe] # (canada fork only): filename to save stream to }