Skip to content

Commit

Permalink
Support argument origin
Browse files Browse the repository at this point in the history
  • Loading branch information
MrTravisB committed Sep 28, 2013
1 parent 38330de commit d45711a
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 18 deletions.
5 changes: 4 additions & 1 deletion tornado/httpserver.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class except to start a server at the beginning of the process
import socket
import ssl
import time
import copy

from tornado.escape import native_str, parse_qs_bytes
from tornado import httputil
Expand Down Expand Up @@ -336,7 +337,7 @@ def _on_request_body(self, data):
if self._request.method in ("POST", "PATCH", "PUT"):
httputil.parse_body_arguments(
self._request.headers.get("Content-Type", ""), data,
self._request.arguments, self._request.files)
self._request.arguments, self._request.body_arguments, self._request.files)
self.request_callback(self._request)


Expand Down Expand Up @@ -457,6 +458,8 @@ def __init__(self, method, uri, version="HTTP/1.0", headers=None,

self.path, sep, self.query = uri.partition('?')
self.arguments = parse_qs_bytes(self.query, keep_blank_values=True)
self.query_arguments = copy.deepcopy(self.arguments)
self.body_arguments = {}

def supports_http_1_1(self):
"""Returns True if this request supports HTTP/1.1 semantics"""
Expand Down
8 changes: 5 additions & 3 deletions tornado/httputil.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ def _int_or_none(val):
return int(val)


def parse_body_arguments(content_type, body, arguments, files):
def parse_body_arguments(content_type, body, arguments, body_arguments, files):
"""Parses a form request body.
Supports ``application/x-www-form-urlencoded`` and
Expand All @@ -324,18 +324,19 @@ def parse_body_arguments(content_type, body, arguments, files):
for name, values in uri_arguments.items():
if values:
arguments.setdefault(name, []).extend(values)
body_arguments.setdefault(name, []).extend(values)
elif content_type.startswith("multipart/form-data"):
fields = content_type.split(";")
for field in fields:
k, sep, v = field.strip().partition("=")
if k == "boundary" and v:
parse_multipart_form_data(utf8(v), body, arguments, files)
parse_multipart_form_data(utf8(v), body, arguments, body_arguments, files)
break
else:
gen_log.warning("Invalid multipart/form-data")


def parse_multipart_form_data(boundary, data, arguments, files):
def parse_multipart_form_data(boundary, data, arguments, body_arguments, files):
"""Parses a ``multipart/form-data`` body.
The ``boundary`` and ``data`` parameters are both byte strings.
Expand Down Expand Up @@ -379,6 +380,7 @@ def parse_multipart_form_data(boundary, data, arguments, files):
content_type=ctype))
else:
arguments.setdefault(name, []).append(value)
body_arguments.setdefault(name, []).append(value)


def format_timestamp(ts):
Expand Down
27 changes: 18 additions & 9 deletions tornado/test/httputil_test.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ def test_file_upload(self):
Foo
--1234--""".replace(b"\n", b"\r\n")
args = {}
body_args = {}
files = {}
parse_multipart_form_data(b"1234", data, args, files)
parse_multipart_form_data(b"1234", data, args, body_args, files)
file = files["files"][0]
self.assertEqual(file["filename"], "ab.txt")
self.assertEqual(file["body"], b"Foo")
Expand All @@ -89,8 +90,9 @@ def test_unquoted_names(self):
Foo
--1234--""".replace(b"\n", b"\r\n")
args = {}
body_args = {}
files = {}
parse_multipart_form_data(b"1234", data, args, files)
parse_multipart_form_data(b"1234", data, args, body_args, files)
file = files["files"][0]
self.assertEqual(file["filename"], "ab.txt")
self.assertEqual(file["body"], b"Foo")
Expand All @@ -114,8 +116,9 @@ def test_special_filenames(self):
--1234--""" % filename.replace('\\', '\\\\').replace('"', '\\"')
data = utf8(data.replace("\n", "\r\n"))
args = {}
body_args = {}
files = {}
parse_multipart_form_data(b"1234", data, args, files)
parse_multipart_form_data(b"1234", data, args, body_args, files)
file = files["files"][0]
self.assertEqual(file["filename"], filename)
self.assertEqual(file["body"], b"Foo")
Expand All @@ -128,8 +131,9 @@ def test_boundary_starts_and_ends_with_quotes(self):
Foo
--1234--'''.replace(b"\n", b"\r\n")
args = {}
body_args = {}
files = {}
parse_multipart_form_data(b'"1234"', data, args, files)
parse_multipart_form_data(b'"1234"', data, args, body_args, files)
file = files["files"][0]
self.assertEqual(file["filename"], "ab.txt")
self.assertEqual(file["body"], b"Foo")
Expand All @@ -141,9 +145,10 @@ def test_missing_headers(self):
Foo
--1234--'''.replace(b"\n", b"\r\n")
args = {}
body_args = {}
files = {}
with ExpectLog(gen_log, "multipart/form-data missing headers"):
parse_multipart_form_data(b"1234", data, args, files)
parse_multipart_form_data(b"1234", data, args, body_args, files)
self.assertEqual(files, {})

def test_invalid_content_disposition(self):
Expand All @@ -154,9 +159,10 @@ def test_invalid_content_disposition(self):
Foo
--1234--'''.replace(b"\n", b"\r\n")
args = {}
body_args = {}
files = {}
with ExpectLog(gen_log, "Invalid multipart/form-data"):
parse_multipart_form_data(b"1234", data, args, files)
parse_multipart_form_data(b"1234", data, args, body_args, files)
self.assertEqual(files, {})

def test_line_does_not_end_with_correct_line_break(self):
Expand All @@ -166,9 +172,10 @@ def test_line_does_not_end_with_correct_line_break(self):
Foo--1234--'''.replace(b"\n", b"\r\n")
args = {}
body_args = {}
files = {}
with ExpectLog(gen_log, "Invalid multipart/form-data"):
parse_multipart_form_data(b"1234", data, args, files)
parse_multipart_form_data(b"1234", data, args, body_args, files)
self.assertEqual(files, {})

def test_content_disposition_header_without_name_parameter(self):
Expand All @@ -179,9 +186,10 @@ def test_content_disposition_header_without_name_parameter(self):
Foo
--1234--""".replace(b"\n", b"\r\n")
args = {}
body_args = {}
files = {}
with ExpectLog(gen_log, "multipart/form-data value missing name"):
parse_multipart_form_data(b"1234", data, args, files)
parse_multipart_form_data(b"1234", data, args, body_args, files)
self.assertEqual(files, {})

def test_data_after_final_boundary(self):
Expand All @@ -196,8 +204,9 @@ def test_data_after_final_boundary(self):
--1234--
""".replace(b"\n", b"\r\n")
args = {}
body_args = {}
files = {}
parse_multipart_form_data(b"1234", data, args, files)
parse_multipart_form_data(b"1234", data, args, body_args, files)
file = files["files"][0]
self.assertEqual(file["filename"], "ab.txt")
self.assertEqual(file["body"], b"Foo")
Expand Down
2 changes: 1 addition & 1 deletion tornado/test/web_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ def get(self, *path_args):
if type(value) != bytes_type:
raise Exception("incorrect type for value: %r" %
type(value))
for value in self.get_arguments(key):
for value in self.get_arguments(key, self.request.arguments):
if type(value) != unicode_type:
raise Exception("incorrect type for value: %r" %
type(value))
Expand Down
37 changes: 34 additions & 3 deletions tornado/web.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -348,14 +348,45 @@ def get_argument(self, name, default=_ARG_DEFAULT, strip=True):
The returned value is always unicode.
"""
args = self.get_arguments(name, strip=strip)
return self._get_argument(name, self.request.arguments, default, strip)

def get_body_argument(self, name, default=_ARG_DEFAULT, strip=True):
"""Returns the value of the argument with the given name
from the request body.
If default is not provided, the argument is considered to be
required, and we raise a `MissingArgumentError` if it is missing.
If the argument appears in the url more than once, we return the
last value.
The returned value is always unicode.
"""
return self._get_argument(name, self.request.body_arguments, default, strip)

def get_query_argument(self, name, default=_ARG_DEFAULT, strip=True):
"""Returns the value of the argument with the given name
from the request query string.
If default is not provided, the argument is considered to be
required, and we raise a `MissingArgumentError` if it is missing.
If the argument appears in the url more than once, we return the
last value.
The returned value is always unicode.
"""
return self._get_argument(name, self.request.query_arguments, default, strip)

def _get_argument(self, name, source, default=_ARG_DEFAULT, strip=True):
args = self.get_arguments(name, source, strip=strip)
if not args:
if default is self._ARG_DEFAULT:
raise MissingArgumentError(name)
return default
return args[-1]

def get_arguments(self, name, strip=True):
def get_arguments(self, name, source, strip=True):
"""Returns a list of the arguments with the given name.
If the argument is not present, returns an empty list.
Expand All @@ -364,7 +395,7 @@ def get_arguments(self, name, strip=True):
"""

values = []
for v in self.request.arguments.get(name, []):
for v in source.get(name, []):
v = self.decode_argument(v, name=name)
if isinstance(v, unicode_type):
# Get rid of any weird control chars (unless decoding gave
Expand Down
6 changes: 5 additions & 1 deletion tornado/wsgi.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import sys
import time
import copy
import tornado

from tornado import escape
Expand Down Expand Up @@ -142,11 +143,14 @@ def __init__(self, environ):
self.path += urllib_parse.quote(from_wsgi_str(environ.get("PATH_INFO", "")))
self.uri = self.path
self.arguments = {}
self.query_arguments = {}
self.body_arguments = {}
self.query = environ.get("QUERY_STRING", "")
if self.query:
self.uri += "?" + self.query
self.arguments = parse_qs_bytes(native_str(self.query),
keep_blank_values=True)
self.query_arguments = copy.deepcopy(self.arguments)
self.version = "HTTP/1.1"
self.headers = httputil.HTTPHeaders()
if environ.get("CONTENT_TYPE"):
Expand All @@ -171,7 +175,7 @@ def __init__(self, environ):
# Parse request body
self.files = {}
httputil.parse_body_arguments(self.headers.get("Content-Type", ""),
self.body, self.arguments, self.files)
self.body, self.arguments, self.body_arguments, self.files)

self._start_time = time.time()
self._finish_time = None
Expand Down

0 comments on commit d45711a

Please sign in to comment.