Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/rest_framework/parsers.py: 42%
120 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1"""
2Parsers are used to parse the content of incoming HTTP requests.
4They give us a generic way of being able to handle various media types
5on the request, such as form content or json encoded data.
6"""
7import codecs
8from urllib import parse
10from django.conf import settings
11from django.core.files.uploadhandler import StopFutureHandlers
12from django.http import QueryDict
13from django.http.multipartparser import ChunkIter
14from django.http.multipartparser import \
15 MultiPartParser as DjangoMultiPartParser
16from django.http.multipartparser import MultiPartParserError, parse_header
17from django.utils.encoding import force_str
19from rest_framework import renderers
20from rest_framework.exceptions import ParseError
21from rest_framework.settings import api_settings
22from rest_framework.utils import json
25class DataAndFiles:
26 def __init__(self, data, files):
27 self.data = data
28 self.files = files
31class BaseParser:
32 """
33 All parsers should extend `BaseParser`, specifying a `media_type`
34 attribute, and overriding the `.parse()` method.
35 """
36 media_type = None
38 def parse(self, stream, media_type=None, parser_context=None):
39 """
40 Given a stream to read from, return the parsed representation.
41 Should return parsed data, or a `DataAndFiles` object consisting of the
42 parsed data and files.
43 """
44 raise NotImplementedError(".parse() must be overridden.")
47class JSONParser(BaseParser):
48 """
49 Parses JSON-serialized data.
50 """
51 media_type = 'application/json'
52 renderer_class = renderers.JSONRenderer
53 strict = api_settings.STRICT_JSON
55 def parse(self, stream, media_type=None, parser_context=None):
56 """
57 Parses the incoming bytestream as JSON and returns the resulting data.
58 """
59 parser_context = parser_context or {}
60 encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
62 try:
63 decoded_stream = codecs.getreader(encoding)(stream)
64 parse_constant = json.strict_constant if self.strict else None
65 return json.load(decoded_stream, parse_constant=parse_constant)
66 except ValueError as exc:
67 raise ParseError('JSON parse error - %s' % str(exc))
70class FormParser(BaseParser):
71 """
72 Parser for form data.
73 """
74 media_type = 'application/x-www-form-urlencoded'
76 def parse(self, stream, media_type=None, parser_context=None):
77 """
78 Parses the incoming bytestream as a URL encoded form,
79 and returns the resulting QueryDict.
80 """
81 parser_context = parser_context or {}
82 encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
83 return QueryDict(stream.read(), encoding=encoding)
86class MultiPartParser(BaseParser):
87 """
88 Parser for multipart form data, which may include file data.
89 """
90 media_type = 'multipart/form-data'
92 def parse(self, stream, media_type=None, parser_context=None):
93 """
94 Parses the incoming bytestream as a multipart encoded form,
95 and returns a DataAndFiles object.
97 `.data` will be a `QueryDict` containing all the form parameters.
98 `.files` will be a `QueryDict` containing all the form files.
99 """
100 parser_context = parser_context or {}
101 request = parser_context['request']
102 encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
103 meta = request.META.copy()
104 meta['CONTENT_TYPE'] = media_type
105 upload_handlers = request.upload_handlers
107 try:
108 parser = DjangoMultiPartParser(meta, stream, upload_handlers, encoding)
109 data, files = parser.parse()
110 return DataAndFiles(data, files)
111 except MultiPartParserError as exc:
112 raise ParseError('Multipart form parse error - %s' % str(exc))
115class FileUploadParser(BaseParser):
116 """
117 Parser for file upload data.
118 """
119 media_type = '*/*'
120 errors = {
121 'unhandled': 'FileUpload parse error - none of upload handlers can handle the stream',
122 'no_filename': 'Missing filename. Request should include a Content-Disposition header with a filename parameter.',
123 }
125 def parse(self, stream, media_type=None, parser_context=None):
126 """
127 Treats the incoming bytestream as a raw file upload and returns
128 a `DataAndFiles` object.
130 `.data` will be None (we expect request body to be a file content).
131 `.files` will be a `QueryDict` containing one 'file' element.
132 """
133 parser_context = parser_context or {}
134 request = parser_context['request']
135 encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
136 meta = request.META
137 upload_handlers = request.upload_handlers
138 filename = self.get_filename(stream, media_type, parser_context)
140 if not filename:
141 raise ParseError(self.errors['no_filename'])
143 # Note that this code is extracted from Django's handling of
144 # file uploads in MultiPartParser.
145 content_type = meta.get('HTTP_CONTENT_TYPE',
146 meta.get('CONTENT_TYPE', ''))
147 try:
148 content_length = int(meta.get('HTTP_CONTENT_LENGTH',
149 meta.get('CONTENT_LENGTH', 0)))
150 except (ValueError, TypeError):
151 content_length = None
153 # See if the handler will want to take care of the parsing.
154 for handler in upload_handlers:
155 result = handler.handle_raw_input(stream,
156 meta,
157 content_length,
158 None,
159 encoding)
160 if result is not None:
161 return DataAndFiles({}, {'file': result[1]})
163 # This is the standard case.
164 possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size]
165 chunk_size = min([2 ** 31 - 4] + possible_sizes)
166 chunks = ChunkIter(stream, chunk_size)
167 counters = [0] * len(upload_handlers)
169 for index, handler in enumerate(upload_handlers):
170 try:
171 handler.new_file(None, filename, content_type,
172 content_length, encoding)
173 except StopFutureHandlers:
174 upload_handlers = upload_handlers[:index + 1]
175 break
177 for chunk in chunks:
178 for index, handler in enumerate(upload_handlers):
179 chunk_length = len(chunk)
180 chunk = handler.receive_data_chunk(chunk, counters[index])
181 counters[index] += chunk_length
182 if chunk is None:
183 break
185 for index, handler in enumerate(upload_handlers):
186 file_obj = handler.file_complete(counters[index])
187 if file_obj is not None:
188 return DataAndFiles({}, {'file': file_obj})
190 raise ParseError(self.errors['unhandled'])
192 def get_filename(self, stream, media_type, parser_context):
193 """
194 Detects the uploaded file name. First searches a 'filename' url kwarg.
195 Then tries to parse Content-Disposition header.
196 """
197 try:
198 return parser_context['kwargs']['filename']
199 except KeyError:
200 pass
202 try:
203 meta = parser_context['request'].META
204 disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode())
205 filename_parm = disposition[1]
206 if 'filename*' in filename_parm:
207 return self.get_encoded_filename(filename_parm)
208 return force_str(filename_parm['filename'])
209 except (AttributeError, KeyError, ValueError):
210 pass
212 def get_encoded_filename(self, filename_parm):
213 """
214 Handle encoded filenames per RFC6266. See also:
215 https://tools.ietf.org/html/rfc2231#section-4
216 """
217 encoded_filename = force_str(filename_parm['filename*'])
218 try:
219 charset, lang, filename = encoded_filename.split('\'', 2)
220 filename = parse.unquote(filename)
221 except (ValueError, LookupError):
222 filename = force_str(filename_parm['filename'])
223 return filename