00001
00002
00003
00004 from __future__ import absolute_import, division, print_function, with_statement
00005 from tornado.httputil import url_concat, parse_multipart_form_data, HTTPHeaders, format_timestamp
00006 from tornado.escape import utf8
00007 from tornado.log import gen_log
00008 from tornado.testing import ExpectLog
00009 from tornado.test.util import unittest
00010
00011 import datetime
00012 import logging
00013 import time
00014
00015
00016 class TestUrlConcat(unittest.TestCase):
00017
00018 def test_url_concat_no_query_params(self):
00019 url = url_concat(
00020 "https://localhost/path",
00021 [('y', 'y'), ('z', 'z')],
00022 )
00023 self.assertEqual(url, "https://localhost/path?y=y&z=z")
00024
00025 def test_url_concat_encode_args(self):
00026 url = url_concat(
00027 "https://localhost/path",
00028 [('y', '/y'), ('z', 'z')],
00029 )
00030 self.assertEqual(url, "https://localhost/path?y=%2Fy&z=z")
00031
00032 def test_url_concat_trailing_q(self):
00033 url = url_concat(
00034 "https://localhost/path?",
00035 [('y', 'y'), ('z', 'z')],
00036 )
00037 self.assertEqual(url, "https://localhost/path?y=y&z=z")
00038
00039 def test_url_concat_q_with_no_trailing_amp(self):
00040 url = url_concat(
00041 "https://localhost/path?x",
00042 [('y', 'y'), ('z', 'z')],
00043 )
00044 self.assertEqual(url, "https://localhost/path?x&y=y&z=z")
00045
00046 def test_url_concat_trailing_amp(self):
00047 url = url_concat(
00048 "https://localhost/path?x&",
00049 [('y', 'y'), ('z', 'z')],
00050 )
00051 self.assertEqual(url, "https://localhost/path?x&y=y&z=z")
00052
00053 def test_url_concat_mult_params(self):
00054 url = url_concat(
00055 "https://localhost/path?a=1&b=2",
00056 [('y', 'y'), ('z', 'z')],
00057 )
00058 self.assertEqual(url, "https://localhost/path?a=1&b=2&y=y&z=z")
00059
00060 def test_url_concat_no_params(self):
00061 url = url_concat(
00062 "https://localhost/path?r=1&t=2",
00063 [],
00064 )
00065 self.assertEqual(url, "https://localhost/path?r=1&t=2")
00066
00067
00068 class MultipartFormDataTest(unittest.TestCase):
00069 def test_file_upload(self):
00070 data = b"""\
00071 --1234
00072 Content-Disposition: form-data; name="files"; filename="ab.txt"
00073
00074 Foo
00075 --1234--""".replace(b"\n", b"\r\n")
00076 args = {}
00077 files = {}
00078 parse_multipart_form_data(b"1234", data, args, files)
00079 file = files["files"][0]
00080 self.assertEqual(file["filename"], "ab.txt")
00081 self.assertEqual(file["body"], b"Foo")
00082
00083 def test_unquoted_names(self):
00084
00085 data = b"""\
00086 --1234
00087 Content-Disposition: form-data; name=files; filename=ab.txt
00088
00089 Foo
00090 --1234--""".replace(b"\n", b"\r\n")
00091 args = {}
00092 files = {}
00093 parse_multipart_form_data(b"1234", data, args, files)
00094 file = files["files"][0]
00095 self.assertEqual(file["filename"], "ab.txt")
00096 self.assertEqual(file["body"], b"Foo")
00097
00098 def test_special_filenames(self):
00099 filenames = ['a;b.txt',
00100 'a"b.txt',
00101 'a";b.txt',
00102 'a;"b.txt',
00103 'a";";.txt',
00104 'a\\"b.txt',
00105 'a\\b.txt',
00106 ]
00107 for filename in filenames:
00108 logging.debug("trying filename %r", filename)
00109 data = """\
00110 --1234
00111 Content-Disposition: form-data; name="files"; filename="%s"
00112
00113 Foo
00114 --1234--""" % filename.replace('\\', '\\\\').replace('"', '\\"')
00115 data = utf8(data.replace("\n", "\r\n"))
00116 args = {}
00117 files = {}
00118 parse_multipart_form_data(b"1234", data, args, files)
00119 file = files["files"][0]
00120 self.assertEqual(file["filename"], filename)
00121 self.assertEqual(file["body"], b"Foo")
00122
00123 def test_boundary_starts_and_ends_with_quotes(self):
00124 data = b'''\
00125 --1234
00126 Content-Disposition: form-data; name="files"; filename="ab.txt"
00127
00128 Foo
00129 --1234--'''.replace(b"\n", b"\r\n")
00130 args = {}
00131 files = {}
00132 parse_multipart_form_data(b'"1234"', data, args, files)
00133 file = files["files"][0]
00134 self.assertEqual(file["filename"], "ab.txt")
00135 self.assertEqual(file["body"], b"Foo")
00136
00137 def test_missing_headers(self):
00138 data = b'''\
00139 --1234
00140
00141 Foo
00142 --1234--'''.replace(b"\n", b"\r\n")
00143 args = {}
00144 files = {}
00145 with ExpectLog(gen_log, "multipart/form-data missing headers"):
00146 parse_multipart_form_data(b"1234", data, args, files)
00147 self.assertEqual(files, {})
00148
00149 def test_invalid_content_disposition(self):
00150 data = b'''\
00151 --1234
00152 Content-Disposition: invalid; name="files"; filename="ab.txt"
00153
00154 Foo
00155 --1234--'''.replace(b"\n", b"\r\n")
00156 args = {}
00157 files = {}
00158 with ExpectLog(gen_log, "Invalid multipart/form-data"):
00159 parse_multipart_form_data(b"1234", data, args, files)
00160 self.assertEqual(files, {})
00161
00162 def test_line_does_not_end_with_correct_line_break(self):
00163 data = b'''\
00164 --1234
00165 Content-Disposition: form-data; name="files"; filename="ab.txt"
00166
00167 Foo--1234--'''.replace(b"\n", b"\r\n")
00168 args = {}
00169 files = {}
00170 with ExpectLog(gen_log, "Invalid multipart/form-data"):
00171 parse_multipart_form_data(b"1234", data, args, files)
00172 self.assertEqual(files, {})
00173
00174 def test_content_disposition_header_without_name_parameter(self):
00175 data = b"""\
00176 --1234
00177 Content-Disposition: form-data; filename="ab.txt"
00178
00179 Foo
00180 --1234--""".replace(b"\n", b"\r\n")
00181 args = {}
00182 files = {}
00183 with ExpectLog(gen_log, "multipart/form-data value missing name"):
00184 parse_multipart_form_data(b"1234", data, args, files)
00185 self.assertEqual(files, {})
00186
00187 def test_data_after_final_boundary(self):
00188
00189
00190
00191 data = b"""\
00192 --1234
00193 Content-Disposition: form-data; name="files"; filename="ab.txt"
00194
00195 Foo
00196 --1234--
00197 """.replace(b"\n", b"\r\n")
00198 args = {}
00199 files = {}
00200 parse_multipart_form_data(b"1234", data, args, files)
00201 file = files["files"][0]
00202 self.assertEqual(file["filename"], "ab.txt")
00203 self.assertEqual(file["body"], b"Foo")
00204
00205
00206 class HTTPHeadersTest(unittest.TestCase):
00207 def test_multi_line(self):
00208
00209
00210
00211
00212 data = """\
00213 Foo: bar
00214 baz
00215 Asdf: qwer
00216 \tzxcv
00217 Foo: even
00218 more
00219 lines
00220 """.replace("\n", "\r\n")
00221 headers = HTTPHeaders.parse(data)
00222 self.assertEqual(headers["asdf"], "qwer zxcv")
00223 self.assertEqual(headers.get_list("asdf"), ["qwer zxcv"])
00224 self.assertEqual(headers["Foo"], "bar baz,even more lines")
00225 self.assertEqual(headers.get_list("foo"), ["bar baz", "even more lines"])
00226 self.assertEqual(sorted(list(headers.get_all())),
00227 [("Asdf", "qwer zxcv"),
00228 ("Foo", "bar baz"),
00229 ("Foo", "even more lines")])
00230
00231
00232 class FormatTimestampTest(unittest.TestCase):
00233
00234 TIMESTAMP = 1359312200.503611
00235 EXPECTED = 'Sun, 27 Jan 2013 18:43:20 GMT'
00236
00237 def check(self, value):
00238 self.assertEqual(format_timestamp(value), self.EXPECTED)
00239
00240 def test_unix_time_float(self):
00241 self.check(self.TIMESTAMP)
00242
00243 def test_unix_time_int(self):
00244 self.check(int(self.TIMESTAMP))
00245
00246 def test_struct_time(self):
00247 self.check(time.gmtime(self.TIMESTAMP))
00248
00249 def test_time_tuple(self):
00250 tup = tuple(time.gmtime(self.TIMESTAMP))
00251 self.assertEqual(9, len(tup))
00252 self.check(tup)
00253
00254 def test_datetime(self):
00255 self.check(datetime.datetime.utcfromtimestamp(self.TIMESTAMP))