Skip to content

Commit

Permalink
Merge branch 'main' into resolve-conflicts-pr-1545
Browse files Browse the repository at this point in the history
  • Loading branch information
bdnettleton committed Feb 21, 2023
2 parents 2e1120c + 7cd6cdc commit 315576f
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 6 deletions.
2 changes: 1 addition & 1 deletion AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,4 @@ The following is a list of much appreciated contributors:
* cocorocho (Erkan Çoban)
* bdnettleton (Brian Nettleton)
* Ptosiek (Antonin)
* samupl (Jakub Szafrański)
* samupl (Jakub Szafrański)
9 changes: 9 additions & 0 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@ Note that if you disable transaction support via configuration (or if your datab
does not support transactions), then validation errors will still be presented to the user
but valid rows will have imported.

``IMPORT_EXPORT_ESCAPE_OUTPUT_ON_EXPORT``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If set to ``True``, export output will be sanitized. By default this is set to ``False``.

Note: currently this only works for ``HTML`` output, and only for exports done via the admin UI.



.. _exampleapp:

Example app
Expand Down
2 changes: 1 addition & 1 deletion import_export/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ def get_export_data(self, file_format, queryset, *args, **kwargs):
raise PermissionDenied

data = self.get_data_for_export(request, queryset, *args, **kwargs)
export_data = file_format.export_data(data)
export_data = file_format.export_data(data, escape_output=self.should_escape_output)
encoding = kwargs.get("encoding")
if not file_format.is_binary() and encoding:
export_data = export_data.encode(encoding)
Expand Down
14 changes: 12 additions & 2 deletions import_export/formats/base_formats.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import html

import tablib
from tablib.formats import registry

Expand All @@ -12,7 +14,7 @@ def create_dataset(self, in_stream):
"""
raise NotImplementedError()

def export_data(self, dataset, **kwargs):
def export_data(self, dataset, escape_output=False, **kwargs):
"""
Returns format representation for given dataset.
"""
Expand Down Expand Up @@ -82,7 +84,7 @@ def get_title(self):
def create_dataset(self, in_stream, **kwargs):
return tablib.import_set(in_stream, format=self.get_title())

def export_data(self, dataset, **kwargs):
def export_data(self, dataset, escape_output=False, **kwargs):
return dataset.export(self.get_title(), **kwargs)

def get_extension(self):
Expand Down Expand Up @@ -145,6 +147,14 @@ class HTML(TextFormat):
TABLIB_MODULE = 'tablib.formats._html'
CONTENT_TYPE = 'text/html'

def export_data(self, dataset, escape_output=False, **kwargs):
if escape_output:
for _ in dataset:
row = dataset.lpop()
row = [html.escape(str(cell)) for cell in row]
dataset.append(row)
return dataset.export(self.get_title(), **kwargs)


class XLS(TablibFormat):
TABLIB_MODULE = 'tablib.formats._xls'
Expand Down
8 changes: 7 additions & 1 deletion import_export/mixins.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import warnings

from django.conf import settings
from django.http import HttpResponse
from django.utils.timezone import now
from django.views.generic.edit import FormView
Expand Down Expand Up @@ -89,6 +90,11 @@ def choose_import_resource_class(self, form):

class BaseExportMixin(BaseImportExportMixin):
model = None
escape_exported_data = False

@property
def should_escape_output(self):
return getattr(settings, 'IMPORT_EXPORT_ESCAPE_OUTPUT_ON_EXPORT', self.escape_exported_data)

def get_export_formats(self):
"""
Expand Down Expand Up @@ -141,7 +147,7 @@ def get_export_data(self, file_format, queryset, *args, **kwargs):
Returns file_format representation for given queryset.
"""
data = self.get_data_for_export(self.request, queryset, *args, **kwargs)
export_data = file_format.export_data(data)
export_data = file_format.export_data(data, escape_output=self.should_escape_output)
return export_data

def get_context_data(self, **kwargs):
Expand Down
39 changes: 38 additions & 1 deletion tests/core/tests/test_base_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,41 @@ def test_get_read_mode(self):
self.assertEqual('r', self.format.get_read_mode())

def test_is_binary(self):
self.assertFalse(self.format.is_binary())
self.assertFalse(self.format.is_binary())


class HTMLFormatTest(TestCase):

def setUp(self):
self.format = base_formats.HTML()
self.dataset = tablib.Dataset(headers=['id', 'username', 'name'])
self.dataset.append((1, 'good_user', 'John Doe'))
self.dataset.append(('2', 'evil_user', '<script>alert("I want to steal your credit card data")</script>'))

def test_export_data_escape(self):
res = self.format.export_data(self.dataset, escape_output=True)
self.assertIn(
(
"<tr><td>1</td>\n"
"<td>good_user</td>\n"
"<td>John Doe</td></tr>\n"
"<tr><td>2</td>\n"
"<td>evil_user</td>\n"
"<td>&lt;script&gt;alert(&quot;I want to steal your credit card data&quot;)&lt;/script&gt;</td></tr>\n"
),
res
)

def test_export_data_no_escape(self):
res = self.format.export_data(self.dataset)
self.assertIn(
(
"<tr><td>1</td>\n"
"<td>good_user</td>\n"
"<td>John Doe</td></tr>\n"
"<tr><td>2</td>\n"
"<td>evil_user</td>\n"
"<td><script>alert(\"I want to steal your credit card data\")</script></td></tr>\n"
),
res
)

0 comments on commit 315576f

Please sign in to comment.