Skip to content

Commit

Permalink
Merge pull request tornadoweb#356 from alekstorm/files
Browse files Browse the repository at this point in the history
Convert dictionaries representing HTTP files to HTTPFile objects
  • Loading branch information
bdarnell committed Sep 10, 2011
2 parents d5e924a + ec953d2 commit 3496f75
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 25 deletions.
5 changes: 1 addition & 4 deletions tornado/httpserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,10 +452,7 @@ class HTTPRequest(object):
.. attribute:: files
File uploads are available in the files property, which maps file
names to list of files. Each file is a dictionary of the form
{"filename":..., "content_type":..., "body":...}. The content_type
comes from the provided HTTP header and should not be trusted
outright given that it can be easily forged.
names to lists of :class:`HTTPFile`.
.. attribute:: connection
Expand Down
17 changes: 15 additions & 2 deletions tornado/httputil.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import urllib
import re

from tornado.util import b
from tornado.util import b, ObjectDict

class HTTPHeaders(dict):
"""A dictionary that maintains Http-Header-Case for all keys.
Expand Down Expand Up @@ -173,6 +173,19 @@ def url_concat(url, args):
url += '&' if ('?' in url) else '?'
return url + urllib.urlencode(args)


class HTTPFile(ObjectDict):
"""Represents an HTTP file. For backwards compatibility, its instance
attributes are also accessible as dictionary keys.
:ivar filename:
:ivar body:
:ivar content_type: The content_type comes from the provided HTTP header
and should not be trusted outright given that it can be easily forged.
"""
pass


def parse_multipart_form_data(boundary, data, arguments, files):
"""Parses a multipart/form-data body.
Expand Down Expand Up @@ -211,7 +224,7 @@ def parse_multipart_form_data(boundary, data, arguments, files):
name = disp_params["name"]
if disp_params.get("filename"):
ctype = headers.get("Content-Type", "application/unknown")
files.setdefault(name, []).append(dict(
files.setdefault(name, []).append(HTTPFile(
filename=disp_params["filename"], body=value,
content_type=ctype))
else:
Expand Down
2 changes: 1 addition & 1 deletion tornado/test/httpserver_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class MultipartTestHandler(RequestHandler):
def post(self):
self.finish({"header": self.request.headers["X-Header-Encoding-Test"],
"argument": self.get_argument("argument"),
"filename": self.request.files["files"][0]["filename"],
"filename": self.request.files["files"][0].filename,
"filebody": _unicode(self.request.files["files"][0]["body"]),
})

Expand Down
6 changes: 3 additions & 3 deletions tornado/test/web_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from tornado.iostream import IOStream
from tornado.template import DictLoader
from tornado.testing import LogTrapTestCase, AsyncHTTPTestCase
from tornado.util import b, bytes_type
from tornado.web import RequestHandler, _O, authenticated, Application, asynchronous, url, HTTPError, StaticFileHandler, _create_signature
from tornado.util import b, bytes_type, ObjectDict
from tornado.web import RequestHandler, authenticated, Application, asynchronous, url, HTTPError, StaticFileHandler, _create_signature

import binascii
import logging
Expand All @@ -17,7 +17,7 @@ class CookieTestRequestHandler(RequestHandler):
def __init__(self):
# don't call super.__init__
self._cookies = {}
self.application = _O(settings=dict(cookie_secret='0123456789'))
self.application = ObjectDict(settings=dict(cookie_secret='0123456789'))

def get_cookie(self, name):
return self._cookies.get(name)
Expand Down
12 changes: 12 additions & 0 deletions tornado/util.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
"""Miscellaneous utility functions."""

class ObjectDict(dict):
"""Makes a dictionary behave like an object."""
def __getattr__(self, name):
try:
return self[name]
except KeyError:
raise AttributeError(name)

def __setattr__(self, name, value):
self[name] = value


def import_object(name):
"""Imports an object by name.
Expand Down
18 changes: 3 additions & 15 deletions tornado/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def get(self):
from tornado import stack_context
from tornado import template
from tornado.escape import utf8, _unicode
from tornado.util import b, bytes_type, import_object
from tornado.util import b, bytes_type, import_object, ObjectDict

try:
from io import BytesIO # python 3
Expand All @@ -105,14 +105,14 @@ def __init__(self, application, request, **kwargs):
self._finished = False
self._auto_finish = True
self._transforms = None # will be set in _execute
self.ui = _O((n, self._ui_method(m)) for n, m in
self.ui = ObjectDict((n, self._ui_method(m)) for n, m in
application.ui_methods.iteritems())
# UIModules are available as both `modules` and `_modules` in the
# template namespace. Historically only `modules` was available
# but could be clobbered by user additions to the namespace.
# The template {% module %} directive looks in `_modules` to avoid
# possible conflicts.
self.ui["_modules"] = _O((n, self._ui_module(n, m)) for n, m in
self.ui["_modules"] = ObjectDict((n, self._ui_module(n, m)) for n, m in
application.ui_modules.iteritems())
self.ui["modules"] = self.ui["_modules"]
self.clear()
Expand Down Expand Up @@ -1911,15 +1911,3 @@ def _create_signature(secret, *parts):
hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
for part in parts: hash.update(utf8(part))
return utf8(hash.hexdigest())


class _O(dict):
"""Makes a dictionary behave like an object."""
def __getattr__(self, name):
try:
return self[name]
except KeyError:
raise AttributeError(name)

def __setattr__(self, name, value):
self[name] = value

0 comments on commit 3496f75

Please sign in to comment.