diff --git a/netbox/core/views.py b/netbox/core/views.py index 78059ce2d..14887eb18 100644 --- a/netbox/core/views.py +++ b/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 diff --git a/netbox/extras/models/mixins.py b/netbox/extras/models/mixins.py index 9663328b5..8ec07bcfb 100644 --- a/netbox/extras/models/mixins.py +++ b/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 diff --git a/netbox/extras/views.py b/netbox/extras/views.py index cec119378..858c0dd63 100644 --- a/netbox/extras/views.py +++ b/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: diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index 84caa2897..3ee716417 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/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 diff --git a/netbox/utilities/export.py b/netbox/utilities/export.py index 5d9dc9826..0df61bc26 100644 --- a/netbox/utilities/export.py +++ b/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