Ticket #36777: views.py

File views.py, 3.5 KB (added by Caram, 4 hours ago)
Line 
1"""
2Custom views for serving files with Unicode filename support.
3"""
4import os
5import mimetypes
6from django.http import FileResponse, Http404
7from django.utils.http import http_date
8from django.views.static import was_modified_since
9from pathlib import Path
10
11
12def serve_unicode(request, path, document_root=None):
13 """
14 Serve static files, handling Unicode filenames correctly under Apache/WSGI.
15
16 This is a modified version of django.views.static.serve that properly
17 handles UTF-8 filenames in Apache/WSGI environments where the default
18 encoding is ASCII.
19 """
20 # Reconstruct the full path
21 path = path.lstrip('/')
22
23 if document_root is not None:
24 # Handle Unicode path encoding issues in Apache/WSGI
25 if isinstance(path, str):
26 try:
27 # Fix UTF-8 mojibake: UTF-8 bytes incorrectly decoded as Latin-1
28 path = path.encode('latin-1').decode('utf-8')
29 except (UnicodeDecodeError, UnicodeEncodeError):
30 # Path is already correctly decoded
31 pass
32
33 # Security check - ensure path doesn't contain '..'
34 if '..' in path.split('/'):
35 raise Http404("Invalid path")
36
37 # Build full path as string first
38 fullpath_str = os.path.join(document_root, path)
39
40 # Normalize the path
41 fullpath_str = os.path.normpath(fullpath_str)
42
43 # Verify it's still within document_root
44 if not fullpath_str.startswith(os.path.normpath(document_root)):
45 raise Http404("Invalid path")
46
47 # Encode to UTF-8 bytes explicitly for filesystem operations
48 try:
49 fullpath_bytes = fullpath_str.encode('utf-8')
50 except UnicodeEncodeError:
51 raise Http404("Invalid filename encoding")
52
53 # Check if file exists using bytes path
54 try:
55 if not os.path.exists(fullpath_bytes):
56 raise Http404("File not found")
57
58 if not os.path.isfile(fullpath_bytes):
59 raise Http404("Not a file")
60 except OSError:
61 raise Http404("File access error")
62
63 # Get file stats using bytes path
64 try:
65 statobj = os.stat(fullpath_bytes)
66 except OSError:
67 raise Http404("Cannot access file")
68
69 # Check if-modified-since header
70 if not was_modified_since(
71 request.META.get('HTTP_IF_MODIFIED_SINCE'),
72 statobj.st_mtime
73 ):
74 from django.http import HttpResponseNotModified
75 return HttpResponseNotModified()
76
77 # Determine content type
78 content_type, encoding = mimetypes.guess_type(fullpath_str)
79 content_type = content_type or 'application/octet-stream'
80
81 # Open file using bytes path to avoid encoding issues
82 try:
83 # Use the byte path for opening
84 response = FileResponse(open(fullpath_bytes, 'rb'), content_type=content_type)
85 except OSError:
86 raise Http404("Cannot read file")
87
88 response['Last-Modified'] = http_date(statobj.st_mtime)
89
90 # Handle Content-Disposition for download
91 if 'filename' in request.GET:
92 filename = request.GET['filename']
93 # Encode filename for Content-Disposition header (RFC 6266)
94 response['Content-Disposition'] = f'attachment; filename*=UTF-8\'\'{filename}'
95
96 response['Content-Length'] = statobj.st_size
97
98 if encoding:
99 response['Content-Encoding'] = encoding
100
101 return response
102
103 raise Http404("Document root not specified")
Back to Top