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

This commit is contained in:
Jeremy Stretch 2026-07-22 11:38:22 -04:00 committed by GitHub
parent abe4a2cd9e
commit e79e33e9dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 12 additions and 6 deletions

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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