Explorar o código

Fixes #22729: Escape names of file attachments in HTTP responses (#22730)

Jeremy Stretch hai 6 días
pai
achega
e79e33e9dd

+ 2 - 1
netbox/core/views.py

@@ -12,6 +12,7 @@ from django.db import DatabaseError, connection
 from django.http import Http404, HttpResponse, HttpResponseForbidden
 from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse
+from django.utils.http import content_disposition_header
 from django.utils.translation import gettext_lazy as _
 from django.views.generic import View
 from django_rq.queues import get_queue_by_index, get_redis_connection
@@ -761,7 +762,7 @@ class SystemView(UserPassesTestMixin, View):
                 },
             }
             response = HttpResponse(json.dumps(data, cls=ConfigJSONEncoder, indent=4), content_type='text/json')
-            response['Content-Disposition'] = 'attachment; filename="netbox.json"'
+            response['Content-Disposition'] = content_disposition_header(as_attachment=True, filename='netbox.json')
             return response
 
         # Serialize any JSON-based classes

+ 3 - 1
netbox/extras/models/mixins.py

@@ -8,6 +8,7 @@ from django.core.exceptions import ValidationError
 from django.core.files.storage import storages
 from django.db import models
 from django.http import HttpResponse
+from django.utils.http import content_disposition_header
 from django.utils.module_loading import import_string
 from django.utils.translation import gettext_lazy as _
 
@@ -250,6 +251,7 @@ class RenderTemplateMixin(models.Model):
                 filename = filename_from_object(context)
             else:
                 filename = "output"
-            response['Content-Disposition'] = f'attachment; filename="{filename}{extension}"'
+            filename = f'{filename}{extension}'
+            response['Content-Disposition'] = content_disposition_header(as_attachment=True, filename=filename)
 
         return response

+ 3 - 2
netbox/extras/views.py

@@ -9,6 +9,7 @@ from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpRespo
 from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse
 from django.utils import timezone
+from django.utils.http import content_disposition_header
 from django.utils.module_loading import import_string
 from django.utils.translation import gettext_lazy as _
 from django.views.generic import View
@@ -1273,7 +1274,7 @@ class ObjectRenderConfigView(generic.ObjectView):
             content = context['rendered_config'] or context['error_message']
             response = HttpResponse(content, content_type='text')
             filename = f"{instance.name or 'config'}.txt"
-            response['Content-Disposition'] = f'attachment; filename="{filename}"'
+            response['Content-Disposition'] = content_disposition_header(as_attachment=True, filename=filename)
             return response
 
         return render(
@@ -1882,7 +1883,7 @@ class ScriptResultView(TableMixin, generic.ObjectView):
             content = (job.data.get("output") or "").encode()
             response = HttpResponse(content, content_type='text')
             filename = f"{job.object.name or 'script-output'}_{job.completed.strftime('%Y-%m-%d_%H%M%S')}.txt"
-            response['Content-Disposition'] = f'attachment; filename="{filename}"'
+            response['Content-Disposition'] = content_disposition_header(as_attachment=True, filename=filename)
             return response
 
         if job.completed:

+ 2 - 1
netbox/netbox/views/generic/bulk_views.py

@@ -14,6 +14,7 @@ from django.db.models.fields.reverse_related import ManyToManyRel
 from django.forms import ModelMultipleChoiceField, MultipleHiddenInput
 from django.http import HttpResponse
 from django.shortcuts import get_object_or_404, redirect, render
+from django.utils.http import content_disposition_header
 from django.utils.safestring import mark_safe
 from django.utils.translation import gettext as _
 from mptt.models import MPTTModel
@@ -197,7 +198,7 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
             if hasattr(model, 'to_yaml'):
                 response = HttpResponse(self.export_yaml(), content_type='text/yaml')
                 filename = 'netbox_{}.yaml'.format(self.queryset.model._meta.verbose_name_plural)
-                response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
+                response['Content-Disposition'] = content_disposition_header(as_attachment=True, filename=filename)
                 return response
 
             # Fall back to default table/YAML export

+ 2 - 1
netbox/utilities/export.py

@@ -2,6 +2,7 @@ import csv
 
 from django.http import StreamingHttpResponse
 from django.utils.encoding import force_str
+from django.utils.http import content_disposition_header
 from django.utils.translation import gettext_lazy as _
 from django_tables2.data import TableQuerysetData
 from django_tables2.export import TableExport as TableExport_
@@ -88,5 +89,5 @@ def stream_table_csv_response(table, exclude_columns=None, filename=None, delimi
 
     response = StreamingHttpResponse(row_generator(), content_type='text/csv; charset=utf-8')
     if filename is not None:
-        response['Content-Disposition'] = f'attachment; filename="{filename}"'
+        response['Content-Disposition'] = content_disposition_header(as_attachment=True, filename=filename)
     return response