""" The :mod:`request` module provides a :class:`Request` class used to wrap the standard `request` object received in all the views. The wrapped request then offers a richer API, in particular : - content automatically parsed according to `Content-Type` header, and available as :meth:`.DATA` - full support of PUT method, including support for file uploads - form overloading of HTTP method, content type and content """ from StringIO import StringIO from djangorestframework import exceptions from djangorestframework.settings import api_settings from djangorestframework.utils.mediatypes import is_form_media_type __all__ = ('Request',) class Empty(object): """ Placeholder for unset attributes. Cannot use `None`, as that may be a valid value. """ pass def _hasattr(obj, name): return not getattr(obj, name) is Empty class Request(object): """ Wrapper allowing to enhance a standard `HttpRequest` instance. Kwargs: - request(HttpRequest). The original request instance. - parsers(list/tuple). The parsers to use for parsing the request content. - authentications(list/tuple). The authentications used to try authenticating the request's user. """ _METHOD_PARAM = api_settings.FORM_METHOD_OVERRIDE _CONTENT_PARAM = api_settings.FORM_CONTENT_OVERRIDE _CONTENTTYPE_PARAM = api_settings.FORM_CONTENTTYPE_OVERRIDE def __init__(self, request, parsers=None, authentication=None): self._request = request self.parsers = parsers or () self.authentication = authentication or () self._data = Empty self._files = Empty self._method = Empty self._content_type = Empty self._stream = Empty def get_parsers(self): """ Instantiates and returns the list of parsers the request will use. """ return [parser() for parser in self.parsers] def get_authentications(self): """ Instantiates and returns the list of parsers the request will use. """ return [authentication() for authentication in self.authentication] @property def method(self): """ Returns the HTTP method. This allows the `method` to be overridden by using a hidden `form` field on a form POST request. """ if not _hasattr(self, '_method'): self._load_method_and_content_type() return self._method @property def content_type(self): """ Returns the content type header. This should be used instead of ``request.META.get('HTTP_CONTENT_TYPE')``, as it allows the content type to be overridden by using a hidden form field on a form POST request. """ if not _hasattr(self, '_content_type'): self._load_method_and_content_type() return self._content_type @property def stream(self): """ Returns an object that may be used to stream the request content. """ if not _hasattr(self, '_stream'): self._load_stream() return self._stream @property def DATA(self): """ Parses the request body and returns the data. Similar to usual behaviour of `request.POST`, except that it handles arbitrary parsers, and also works on methods other than POST (eg PUT). """ if not _hasattr(self, '_data'): self._load_data_and_files() return self._data @property def FILES(self): """ Parses the request body and returns any files uploaded in the request. Similar to usual behaviour of `request.FILES`, except that it handles arbitrary parsers, and also works on methods other than POST (eg PUT). """ if not _hasattr(self, '_files'): self._load_data_and_files() return self._files @property def user(self): """ Returns the user associated with the current request, as authenticated by the authentication classes provided to the request. """ if not hasattr(self, '_user'): self._user, self._auth = self._authenticate() return self._user @property def auth(self): """ Returns any non-user authentication information associated with the request, such as an authentication token. """ if not hasattr(self, '_auth'): self._user, self._auth = self._authenticate() return self._auth def _load_data_and_files(self): """ Parses the request content into self.DATA and self.FILES. """ if not _hasattr(self, '_content_type'): self._load_method_and_content_type() if not _hasattr(self, '_data'): self._data, self._files = self._parse() def _load_method_and_content_type(self): """ Sets the method and content_type, and then check if they've been overridden. """ self._content_type = self.META.get('HTTP_CONTENT_TYPE', self.META.get('CONTENT_TYPE', '')) self._perform_form_overloading() # if the HTTP method was not overloaded, we take the raw HTTP method if not _hasattr(self, '_method'): self._method = self._request.method def _load_stream(self): """ Return the content body of the request, as a stream. """ try: content_length = int(self.META.get('CONTENT_LENGTH', self.META.get('HTTP_CONTENT_LENGTH'))) except (ValueError, TypeError): content_length = 0 if content_length == 0: self._stream = None elif hasattr(self._request, 'read'): self._stream = self._request else: self._stream = StringIO(self.raw_post_data) def _perform_form_overloading(self): """ If this is a form POST request, then we need to check if the method and content/content_type have been overridden by setting them in hidden form fields or not. """ USE_FORM_OVERLOADING = ( self._METHOD_PARAM or (self._CONTENT_PARAM and self._CONTENTTYPE_PARAM) ) # We only need to use form overloading on form POST requests. if (not USE_FORM_OVERLOADING or self._request.method != 'POST' or not is_form_media_type(self._content_type)): return # At this point we're committed to parsing the request as form data. self._data = self._request.POST self._files = self._request.FILES # Method overloading - change the method and remove the param from the content. if (self._METHOD_PARAM and self._METHOD_PARAM in self._data): self._method = self._data[self._METHOD_PARAM].upper() self._data.pop(self._METHOD_PARAM) # Content overloading - modify the content type, and re-parse. if (self._CONTENT_PARAM and self._CONTENTTYPE_PARAM and self._CONTENT_PARAM in self._data and self._CONTENTTYPE_PARAM in self._data): self._content_type = self._data[self._CONTENTTYPE_PARAM] self._stream = StringIO(self._data[self._CONTENT_PARAM]) self._data.pop(self._CONTENTTYPE_PARAM) self._data.pop(self._CONTENT_PARAM) self._data, self._files = self._parse() def _parse(self): """ Parse the request content, returning a two-tuple of (data, files) May raise an `UnsupportedMediaType`, or `ParseError` exception. """ if self.stream is None or self.content_type is None: return (None, None) for parser in self.get_parsers(): if parser.can_handle_request(self.content_type): parsed = parser.parse(self.stream, meta=self.META, upload_handlers=self.upload_handlers) # Parser classes may return the raw data, or a # DataAndFiles object. Unpack the result as required. try: return (parsed.data, parsed.files) except AttributeError: return (parsed, None) raise exceptions.UnsupportedMediaType(self._content_type) def _authenticate(self): """ Attempt to authenticate the request using each authentication instance in turn. Returns a two-tuple of (user, authtoken). """ for authentication in self.get_authentications(): user_auth_tuple = authentication.authenticate(self) if not user_auth_tuple is None: return user_auth_tuple return self._not_authenticated() def _not_authenticated(self): """ Return a two-tuple of (user, authtoken), representing an unauthenticated request. By default this will be (AnonymousUser, None). """ if api_settings.UNAUTHENTICATED_USER: user = api_settings.UNAUTHENTICATED_USER() else: user = None if api_settings.UNAUTHENTICATED_TOKEN: auth = api_settings.UNAUTHENTICATED_TOKEN() else: auth = None return (user, auth) def __getattr__(self, attr): """ Proxy other attributes to the underlying HttpRequest object. """ return getattr(self._request, attr) > 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281