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

1""" 

2Parsers are used to parse the content of incoming HTTP requests. 

3 

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 

9 

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 

18 

19from rest_framework import renderers 

20from rest_framework.exceptions import ParseError 

21from rest_framework.settings import api_settings 

22from rest_framework.utils import json 

23 

24 

25class DataAndFiles: 

26 def __init__(self, data, files): 

27 self.data = data 

28 self.files = files 

29 

30 

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 

37 

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.") 

45 

46 

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 

54 

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) 

61 

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

68 

69 

70class FormParser(BaseParser): 

71 """ 

72 Parser for form data. 

73 """ 

74 media_type = 'application/x-www-form-urlencoded' 

75 

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) 

84 

85 

86class MultiPartParser(BaseParser): 

87 """ 

88 Parser for multipart form data, which may include file data. 

89 """ 

90 media_type = 'multipart/form-data' 

91 

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. 

96 

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 

106 

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

113 

114 

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 } 

124 

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. 

129 

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) 

139 

140 if not filename: 

141 raise ParseError(self.errors['no_filename']) 

142 

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 

152 

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]}) 

162 

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) 

168 

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 

176 

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 

184 

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}) 

189 

190 raise ParseError(self.errors['unhandled']) 

191 

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 

201 

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 

211 

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