Merge lp:~gholt/swift/xmlcontrolchars into lp:~hudson-openstack/swift/trunk
- xmlcontrolchars
- Merge into trunk
Proposed by
gholt
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | John Dickinson | ||||
Approved revision: | 317 | ||||
Merged at revision: | 319 | ||||
Proposed branch: | lp:~gholt/swift/xmlcontrolchars | ||||
Merge into: | lp:~hudson-openstack/swift/trunk | ||||
Diff against target: |
526 lines (+236/-35) 10 files modified
swift/account/server.py (+7/-4) swift/common/constraints.py (+6/-3) swift/common/utils.py (+5/-2) swift/container/server.py (+11/-5) test/functionalnosetests/test_account.py (+27/-0) test/functionalnosetests/test_container.py (+44/-0) test/functionalnosetests/test_object.py (+24/-0) test/unit/account/test_server.py (+54/-6) test/unit/container/test_server.py (+44/-15) test/unit/proxy/test_server.py (+14/-0) |
||||
To merge this branch: | bzr merge lp:~gholt/swift/xmlcontrolchars | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John Dickinson | Approve | ||
David Goetz (community) | Approve | ||
Review via email: mp+64926@code.launchpad.net |
Commit message
Description of the change
Update to comply with XML 1.1: No NULLs in names allowed; control chars are converted to entities (ex: )
To post a comment you must log in.
lp:~gholt/swift/xmlcontrolchars
updated
Revision history for this message
John Dickinson (notmyname) wrote : | # |
looks good to me
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'swift/account/server.py' |
2 | --- swift/account/server.py 2011-06-10 18:36:02 +0000 |
3 | +++ swift/account/server.py 2011-06-17 01:04:33 +0000 |
4 | @@ -29,7 +29,7 @@ |
5 | |
6 | from swift.common.db import AccountBroker |
7 | from swift.common.utils import get_logger, get_param, hash_path, \ |
8 | - normalize_timestamp, split_path, storage_directory |
9 | + normalize_timestamp, split_path, storage_directory, XML_EXTRA_ENTITIES |
10 | from swift.common.constraints import ACCOUNT_LISTING_LIMIT, \ |
11 | check_mount, check_float, check_utf8 |
12 | from swift.common.db_replicator import ReplicatorRpc |
13 | @@ -79,6 +79,9 @@ |
14 | try: |
15 | drive, part, account, container = split_path(unquote(req.path), |
16 | 3, 4) |
17 | + if (account and not check_utf8(account)) or \ |
18 | + (container and not check_utf8(container)): |
19 | + raise ValueError('NULL characters not allowed in names') |
20 | except ValueError, err: |
21 | return HTTPBadRequest(body=str(err), content_type='text/plain', |
22 | request=req) |
23 | @@ -201,8 +204,8 @@ |
24 | marker = get_param(req, 'marker', '') |
25 | end_marker = get_param(req, 'end_marker') |
26 | query_format = get_param(req, 'format') |
27 | - except UnicodeDecodeError, err: |
28 | - return HTTPBadRequest(body='parameters not utf8', |
29 | + except (UnicodeDecodeError, ValueError), err: |
30 | + return HTTPBadRequest(body='parameters not utf8 or contain NULLs', |
31 | content_type='text/plain', request=req) |
32 | if query_format: |
33 | req.accept = 'application/%s' % query_format.lower() |
34 | @@ -228,7 +231,7 @@ |
35 | output_list = ['<?xml version="1.0" encoding="UTF-8"?>', |
36 | '<account name="%s">' % account] |
37 | for (name, object_count, bytes_used, is_subdir) in account_list: |
38 | - name = saxutils.escape(name) |
39 | + name = saxutils.escape(name, XML_EXTRA_ENTITIES) |
40 | if is_subdir: |
41 | output_list.append('<subdir name="%s" />' % name) |
42 | else: |
43 | |
44 | === modified file 'swift/common/constraints.py' |
45 | --- swift/common/constraints.py 2011-01-04 23:34:43 +0000 |
46 | +++ swift/common/constraints.py 2011-06-17 01:04:33 +0000 |
47 | @@ -159,13 +159,16 @@ |
48 | |
49 | def check_utf8(string): |
50 | """ |
51 | - Validate if a string is valid UTF-8. |
52 | + Validate if a string is valid UTF-8 and has no NULL characters. |
53 | |
54 | :param string: string to be validated |
55 | - :returns: True if the string is valid utf-8, False otherwise |
56 | + :returns: True if the string is valid utf-8 and has no NULL characters, |
57 | + False otherwise |
58 | """ |
59 | try: |
60 | string.decode('UTF-8') |
61 | - return True |
62 | except UnicodeDecodeError: |
63 | return False |
64 | + if '\x00' in string: |
65 | + return False |
66 | + return True |
67 | |
68 | === modified file 'swift/common/utils.py' |
69 | --- swift/common/utils.py 2011-06-05 23:22:35 +0000 |
70 | +++ swift/common/utils.py 2011-06-17 01:04:33 +0000 |
71 | @@ -42,6 +42,7 @@ |
72 | from eventlet.green import socket, subprocess, ssl, thread, threading |
73 | import netifaces |
74 | |
75 | +from swift.common.constraints import check_utf8 |
76 | from swift.common.exceptions import LockTimeout, MessageTimeout |
77 | |
78 | # logging doesn't import patched as cleanly as one would like |
79 | @@ -74,6 +75,8 @@ |
80 | # Used when reading config values |
81 | TRUE_VALUES = set(('true', '1', 'yes', 'on', 't', 'y')) |
82 | |
83 | +# Used with xml.sax.saxutils.escape |
84 | +XML_EXTRA_ENTITIES = dict((chr(x), '&#x%x;' % x) for x in xrange(1, 0x20)) |
85 | |
86 | def validate_configuration(): |
87 | if HASH_PATH_SUFFIX == '': |
88 | @@ -110,8 +113,8 @@ |
89 | :returns: HTTP request parameter value |
90 | """ |
91 | value = req.str_params.get(name, default) |
92 | - if value: |
93 | - value.decode('utf8') # Ensure UTF8ness |
94 | + if value and not check_utf8(value): |
95 | + raise ValueError('Not valid UTF-8 or contains NULL characters') |
96 | return value |
97 | |
98 | |
99 | |
100 | === modified file 'swift/container/server.py' |
101 | --- swift/container/server.py 2011-06-10 18:36:02 +0000 |
102 | +++ swift/container/server.py 2011-06-17 01:04:33 +0000 |
103 | @@ -32,7 +32,7 @@ |
104 | |
105 | from swift.common.db import ContainerBroker |
106 | from swift.common.utils import get_logger, get_param, hash_path, \ |
107 | - normalize_timestamp, storage_directory, split_path |
108 | + normalize_timestamp, storage_directory, split_path, XML_EXTRA_ENTITIES |
109 | from swift.common.constraints import CONTAINER_LISTING_LIMIT, \ |
110 | check_mount, check_float, check_utf8 |
111 | from swift.common.bufferedhttp import http_connect |
112 | @@ -167,6 +167,10 @@ |
113 | try: |
114 | drive, part, account, container, obj = split_path( |
115 | unquote(req.path), 4, 5, True) |
116 | + if (account and not check_utf8(account)) or \ |
117 | + (container and not check_utf8(container)) or \ |
118 | + (obj and not check_utf8(obj)): |
119 | + raise ValueError('NULL characters not allowed in names') |
120 | except ValueError, err: |
121 | return HTTPBadRequest(body=str(err), content_type='text/plain', |
122 | request=req) |
123 | @@ -277,7 +281,7 @@ |
124 | return HTTPPreconditionFailed(request=req, |
125 | body='Maximum limit is %d' % CONTAINER_LISTING_LIMIT) |
126 | query_format = get_param(req, 'format') |
127 | - except UnicodeDecodeError, err: |
128 | + except (UnicodeDecodeError, ValueError), err: |
129 | return HTTPBadRequest(body='parameters not utf8', |
130 | content_type='text/plain', request=req) |
131 | if query_format: |
132 | @@ -312,21 +316,23 @@ |
133 | xml_output = [] |
134 | for (name, created_at, size, content_type, etag) in container_list: |
135 | # escape name and format date here |
136 | - name = saxutils.escape(name) |
137 | + name = saxutils.escape(name, XML_EXTRA_ENTITIES) |
138 | created_at = datetime.utcfromtimestamp( |
139 | float(created_at)).isoformat() |
140 | if content_type is None: |
141 | xml_output.append('<subdir name="%s"><name>%s</name>' |
142 | '</subdir>' % (name, name)) |
143 | else: |
144 | - content_type = saxutils.escape(content_type) |
145 | + content_type = saxutils.escape(content_type, |
146 | + XML_EXTRA_ENTITIES) |
147 | xml_output.append('<object><name>%s</name><hash>%s</hash>'\ |
148 | '<bytes>%d</bytes><content_type>%s</content_type>'\ |
149 | '<last_modified>%s</last_modified></object>' % \ |
150 | (name, etag, size, content_type, created_at)) |
151 | container_list = ''.join([ |
152 | '<?xml version="1.0" encoding="UTF-8"?>\n', |
153 | - '<container name=%s>' % saxutils.quoteattr(container), |
154 | + '<container name=%s>' % |
155 | + saxutils.quoteattr(container, XML_EXTRA_ENTITIES), |
156 | ''.join(xml_output), '</container>']) |
157 | else: |
158 | if not container_list: |
159 | |
160 | === modified file 'test/functionalnosetests/test_account.py' |
161 | --- test/functionalnosetests/test_account.py 2010-09-03 16:20:28 +0000 |
162 | +++ test/functionalnosetests/test_account.py 2011-06-17 01:04:33 +0000 |
163 | @@ -2,6 +2,7 @@ |
164 | |
165 | import unittest |
166 | from nose import SkipTest |
167 | +from uuid import uuid4 |
168 | |
169 | from swift.common.constraints import MAX_META_COUNT, MAX_META_NAME_LENGTH, \ |
170 | MAX_META_OVERALL_SIZE, MAX_META_VALUE_LENGTH |
171 | @@ -132,6 +133,32 @@ |
172 | resp.read() |
173 | self.assertEquals(resp.status, 400) |
174 | |
175 | + def test_name_control_chars(self): |
176 | + if skip: |
177 | + raise SkipTest |
178 | + |
179 | + container = uuid4().hex |
180 | + |
181 | + def put(url, token, parsed, conn): |
182 | + conn.request('PUT', '%s/%s%%01test' % |
183 | + (parsed.path, container), '', |
184 | + {'X-Auth-Token': token, 'Content-Length': '0'}) |
185 | + return check_response(conn) |
186 | + |
187 | + resp = retry(put) |
188 | + resp.read() |
189 | + self.assertTrue(resp.status in (201, 202)) |
190 | + |
191 | + def get(url, token, parsed, conn): |
192 | + conn.request('GET', '%s?format=xml' % (parsed.path,), '', |
193 | + {'X-Auth-Token': token}) |
194 | + return check_response(conn) |
195 | + |
196 | + resp = retry(get) |
197 | + body = resp.read() |
198 | + self.assertEquals(resp.status, 200) |
199 | + self.assertTrue('<name>%stest</name>' % (container,) in body) |
200 | + |
201 | |
202 | if __name__ == '__main__': |
203 | unittest.main() |
204 | |
205 | === modified file 'test/functionalnosetests/test_container.py' |
206 | --- test/functionalnosetests/test_container.py 2011-03-24 23:12:36 +0000 |
207 | +++ test/functionalnosetests/test_container.py 2011-06-17 01:04:33 +0000 |
208 | @@ -522,6 +522,50 @@ |
209 | resp.read() |
210 | self.assertEquals(resp.status, 201) |
211 | |
212 | + def test_name_control_chars(self): |
213 | + if skip: |
214 | + raise SkipTest |
215 | + |
216 | + def put(url, token, parsed, conn): |
217 | + conn.request('PUT', '%s/%s%%00test' % (parsed.path, self.name), '', |
218 | + {'X-Auth-Token': token}) |
219 | + return check_response(conn) |
220 | + |
221 | + resp = retry(put) |
222 | + resp.read() |
223 | + # NULLs not allowed |
224 | + self.assertEquals(resp.status, 412) |
225 | + |
226 | + def put(url, token, parsed, conn): |
227 | + conn.request('PUT', '%s/%s%%01test' % (parsed.path, self.name), '', |
228 | + {'X-Auth-Token': token}) |
229 | + return check_response(conn) |
230 | + |
231 | + resp = retry(put) |
232 | + resp.read() |
233 | + # 0x01 allowed |
234 | + self.assertTrue(resp.status in (201, 202)) |
235 | + |
236 | + def put(url, token, parsed, conn): |
237 | + conn.request('PUT', '%s/%s/object%%01test' % |
238 | + (parsed.path, self.name), '', |
239 | + {'X-Auth-Token': token, 'Content-Length': '0'}) |
240 | + return check_response(conn) |
241 | + |
242 | + resp = retry(put) |
243 | + resp.read() |
244 | + self.assertTrue(resp.status in (201, 202)) |
245 | + |
246 | + def get(url, token, parsed, conn): |
247 | + conn.request('GET', '%s/%s?format=xml' % (parsed.path, self.name), |
248 | + '', {'X-Auth-Token': token}) |
249 | + return check_response(conn) |
250 | + |
251 | + resp = retry(get) |
252 | + body = resp.read() |
253 | + self.assertEquals(resp.status, 200) |
254 | + self.assertTrue('<name>objecttest</name>' in body) |
255 | + |
256 | |
257 | if __name__ == '__main__': |
258 | unittest.main() |
259 | |
260 | === modified file 'test/functionalnosetests/test_object.py' |
261 | --- test/functionalnosetests/test_object.py 2010-12-28 19:33:36 +0000 |
262 | +++ test/functionalnosetests/test_object.py 2011-06-17 01:04:33 +0000 |
263 | @@ -541,6 +541,30 @@ |
264 | resp.read() |
265 | self.assertEquals(resp.status, 204) |
266 | |
267 | + def test_name_control_chars(self): |
268 | + if skip: |
269 | + raise SkipTest |
270 | + |
271 | + def put(url, token, parsed, conn): |
272 | + conn.request('PUT', '%s/%s/obj%%00test' % (parsed.path, |
273 | + self.container), 'test', {'X-Auth-Token': token}) |
274 | + return check_response(conn) |
275 | + |
276 | + resp = retry(put) |
277 | + resp.read() |
278 | + # NULLs not allowed |
279 | + self.assertEquals(resp.status, 412) |
280 | + |
281 | + def put(url, token, parsed, conn): |
282 | + conn.request('PUT', '%s/%s/obj%%01test' % (parsed.path, |
283 | + self.container), 'test', {'X-Auth-Token': token}) |
284 | + return check_response(conn) |
285 | + |
286 | + resp = retry(put) |
287 | + resp.read() |
288 | + # 0x01 allowed |
289 | + self.assertEquals(resp.status, 201) |
290 | + |
291 | |
292 | if __name__ == '__main__': |
293 | unittest.main() |
294 | |
295 | === modified file 'test/unit/account/test_server.py' |
296 | --- test/unit/account/test_server.py 2011-06-10 18:36:02 +0000 |
297 | +++ test/unit/account/test_server.py 2011-06-17 01:04:33 +0000 |
298 | @@ -22,6 +22,7 @@ |
299 | import simplejson |
300 | import xml.dom.minidom |
301 | from webob import Request |
302 | +from xml.parsers.expat import ExpatError |
303 | |
304 | from swift.account.server import AccountController, ACCOUNT_LISTING_LIMIT |
305 | from swift.common.utils import normalize_timestamp |
306 | @@ -450,7 +451,8 @@ |
307 | 'X-Bytes-Used': '0', |
308 | 'X-Timestamp': normalize_timestamp(0)}) |
309 | self.controller.PUT(req) |
310 | - req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'PUT'}, |
311 | + req = Request.blank('/sda1/p/a/c2%04', |
312 | + environ={'REQUEST_METHOD': 'PUT'}, |
313 | headers={'X-Put-Timestamp': '2', |
314 | 'X-Delete-Timestamp': '0', |
315 | 'X-Object-Count': '0', |
316 | @@ -462,7 +464,15 @@ |
317 | resp = self.controller.GET(req) |
318 | self.assertEquals(resp.content_type, 'application/xml') |
319 | self.assertEquals(resp.status_int, 200) |
320 | - dom = xml.dom.minidom.parseString(resp.body) |
321 | + try: |
322 | + dom = xml.dom.minidom.parseString(resp.body) |
323 | + except ExpatError, err: |
324 | + # Expat doesn't like control characters, which are XML 1.1 |
325 | + # compatible. Soooo, we have to replace them. We'll do a specific |
326 | + # replace in this case, but real code that uses Expat will need |
327 | + # something more resilient. |
328 | + dom = xml.dom.minidom.parseString( |
329 | + resp.body.replace('', '\\x04')) |
330 | self.assertEquals(dom.firstChild.nodeName, 'account') |
331 | listing = \ |
332 | [n for n in dom.firstChild.childNodes if n.nodeName != '#text'] |
333 | @@ -483,7 +493,7 @@ |
334 | self.assertEquals(sorted([n.nodeName for n in container]), |
335 | ['bytes', 'count', 'name']) |
336 | node = [n for n in container if n.nodeName == 'name'][0] |
337 | - self.assertEquals(node.firstChild.nodeValue, 'c2') |
338 | + self.assertEquals(node.firstChild.nodeValue, 'c2\\x04') |
339 | node = [n for n in container if n.nodeName == 'count'][0] |
340 | self.assertEquals(node.firstChild.nodeValue, '0') |
341 | node = [n for n in container if n.nodeName == 'bytes'][0] |
342 | @@ -495,7 +505,8 @@ |
343 | 'X-Bytes-Used': '2', |
344 | 'X-Timestamp': normalize_timestamp(0)}) |
345 | self.controller.PUT(req) |
346 | - req = Request.blank('/sda1/p/a/c2', environ={'REQUEST_METHOD': 'PUT'}, |
347 | + req = Request.blank('/sda1/p/a/c2%04', |
348 | + environ={'REQUEST_METHOD': 'PUT'}, |
349 | headers={'X-Put-Timestamp': '2', |
350 | 'X-Delete-Timestamp': '0', |
351 | 'X-Object-Count': '3', |
352 | @@ -506,7 +517,15 @@ |
353 | environ={'REQUEST_METHOD': 'GET'}) |
354 | resp = self.controller.GET(req) |
355 | self.assertEquals(resp.status_int, 200) |
356 | - dom = xml.dom.minidom.parseString(resp.body) |
357 | + try: |
358 | + dom = xml.dom.minidom.parseString(resp.body) |
359 | + except ExpatError, err: |
360 | + # Expat doesn't like control characters, which are XML 1.1 |
361 | + # compatible. Soooo, we have to replace them. We'll do a specific |
362 | + # replace in this case, but real code that uses Expat will need |
363 | + # something more resilient. |
364 | + dom = xml.dom.minidom.parseString( |
365 | + resp.body.replace('', '\\x04')) |
366 | self.assertEquals(dom.firstChild.nodeName, 'account') |
367 | listing = \ |
368 | [n for n in dom.firstChild.childNodes if n.nodeName != '#text'] |
369 | @@ -526,7 +545,7 @@ |
370 | self.assertEquals(sorted([n.nodeName for n in container]), |
371 | ['bytes', 'count', 'name']) |
372 | node = [n for n in container if n.nodeName == 'name'][0] |
373 | - self.assertEquals(node.firstChild.nodeValue, 'c2') |
374 | + self.assertEquals(node.firstChild.nodeValue, 'c2\\x04') |
375 | node = [n for n in container if n.nodeName == 'count'][0] |
376 | self.assertEquals(node.firstChild.nodeValue, '3') |
377 | node = [n for n in container if n.nodeName == 'bytes'][0] |
378 | @@ -959,6 +978,35 @@ |
379 | resp = self.controller.GET(req) |
380 | self.assert_(resp.status_int in (204, 412), resp.status_int) |
381 | |
382 | + def test_params_no_null(self): |
383 | + self.controller.PUT(Request.blank('/sda1/p/a', |
384 | + headers={'X-Timestamp': normalize_timestamp(1)}, |
385 | + environ={'REQUEST_METHOD': 'PUT'})) |
386 | + for param in ('delimiter', 'format', 'limit', 'marker', |
387 | + 'prefix'): |
388 | + req = Request.blank('/sda1/p/a?%s=\x00' % param, |
389 | + environ={'REQUEST_METHOD': 'GET'}) |
390 | + resp = self.controller.GET(req) |
391 | + self.assertEquals(resp.status_int, 400) |
392 | + |
393 | + def test_PUT_account_no_null(self): |
394 | + req = Request.blank('/sda1/p/test\x00test', |
395 | + environ={'REQUEST_METHOD': 'PUT', 'HTTP_X_TIMESTAMP': '1'}) |
396 | + resp = self.controller.PUT(req) |
397 | + self.assertEquals(resp.status_int, 400) |
398 | + |
399 | + def test_PUT_container_no_null(self): |
400 | + req = Request.blank('/sda1/p/a', |
401 | + environ={'REQUEST_METHOD': 'PUT', 'HTTP_X_TIMESTAMP': '1'}) |
402 | + resp = self.controller.PUT(req) |
403 | + self.assertEquals(resp.status_int, 201) |
404 | + req = Request.blank('/sda1/p/a/test\x00test', |
405 | + environ={'REQUEST_METHOD': 'PUT', 'HTTP_X_PUT_TIMESTAMP': '1', |
406 | + 'HTTP_X_DELETE_TIMESTAMP': '0', |
407 | + 'HTTP_X_OBJECT_COUNT': '0', 'HTTP_X_BYTES_USED': '0'}) |
408 | + resp = self.controller.PUT(req) |
409 | + self.assertEquals(resp.status_int, 400) |
410 | + |
411 | |
412 | if __name__ == '__main__': |
413 | unittest.main() |
414 | |
415 | === modified file 'test/unit/container/test_server.py' |
416 | --- test/unit/container/test_server.py 2011-06-10 18:36:02 +0000 |
417 | +++ test/unit/container/test_server.py 2011-06-17 01:04:33 +0000 |
418 | @@ -246,6 +246,24 @@ |
419 | resp = self.controller.PUT(req) |
420 | self.assertEquals(resp.status_int, 201) |
421 | |
422 | + def test_PUT_container_no_null(self): |
423 | + req = Request.blank('/sda1/p/a/test\x00test', |
424 | + environ={'REQUEST_METHOD': 'PUT', 'HTTP_X_TIMESTAMP': '1'}) |
425 | + resp = self.controller.PUT(req) |
426 | + self.assertEquals(resp.status_int, 400) |
427 | + |
428 | + def test_PUT_object_no_null(self): |
429 | + req = Request.blank('/sda1/p/a/test', |
430 | + environ={'REQUEST_METHOD': 'PUT', 'HTTP_X_TIMESTAMP': '1'}) |
431 | + resp = self.controller.PUT(req) |
432 | + self.assertEquals(resp.status_int, 201) |
433 | + req = Request.blank('/sda1/p/a/test/test\x00test', |
434 | + environ={'REQUEST_METHOD': 'PUT', 'HTTP_X_TIMESTAMP': '1', |
435 | + 'HTTP_X_SIZE': '0', 'HTTP_X_CONTENT_TYPE': 'text/plain', |
436 | + 'HTTP_X_ETAG': 'd41d8cd98f00b204e9800998ecf8427e'}) |
437 | + resp = self.controller.PUT(req) |
438 | + self.assertEquals(resp.status_int, 400) |
439 | + |
440 | def test_PUT_account_update(self): |
441 | bindsock = listen(('127.0.0.1', 0)) |
442 | def accept(return_code, expected_timestamp): |
443 | @@ -582,25 +600,25 @@ |
444 | resp = self.controller.PUT(req) |
445 | # fill the container |
446 | for i in range(3): |
447 | - req = Request.blank('/sda1/p/a/xmlc/%s'%i, environ= |
448 | - {'REQUEST_METHOD': 'PUT', |
449 | - 'HTTP_X_TIMESTAMP': '1', |
450 | - 'HTTP_X_CONTENT_TYPE': 'text/plain', |
451 | - 'HTTP_X_ETAG': 'x', |
452 | - 'HTTP_X_SIZE': 0}) |
453 | + req = Request.blank('/sda1/p/a/xmlc/%s%%%02x' % (i, i + 1), |
454 | + environ={'REQUEST_METHOD': 'PUT', |
455 | + 'HTTP_X_TIMESTAMP': '1', |
456 | + 'HTTP_X_CONTENT_TYPE': 'text/plain', |
457 | + 'HTTP_X_ETAG': 'x', |
458 | + 'HTTP_X_SIZE': 0}) |
459 | resp = self.controller.PUT(req) |
460 | self.assertEquals(resp.status_int, 201) |
461 | xml_body = '<?xml version="1.0" encoding="UTF-8"?>\n' \ |
462 | '<container name="xmlc">' \ |
463 | - '<object><name>0</name><hash>x</hash><bytes>0</bytes>' \ |
464 | - '<content_type>text/plain</content_type>' \ |
465 | - '<last_modified>1970-01-01T00:00:01' \ |
466 | - '</last_modified></object>' \ |
467 | - '<object><name>1</name><hash>x</hash><bytes>0</bytes>' \ |
468 | - '<content_type>text/plain</content_type>' \ |
469 | - '<last_modified>1970-01-01T00:00:01' \ |
470 | - '</last_modified></object>' \ |
471 | - '<object><name>2</name><hash>x</hash><bytes>0</bytes>' \ |
472 | + '<object><name>0</name><hash>x</hash><bytes>0</bytes>' \ |
473 | + '<content_type>text/plain</content_type>' \ |
474 | + '<last_modified>1970-01-01T00:00:01' \ |
475 | + '</last_modified></object>' \ |
476 | + '<object><name>1</name><hash>x</hash><bytes>0</bytes>' \ |
477 | + '<content_type>text/plain</content_type>' \ |
478 | + '<last_modified>1970-01-01T00:00:01' \ |
479 | + '</last_modified></object>' \ |
480 | + '<object><name>2</name><hash>x</hash><bytes>0</bytes>' \ |
481 | '<content_type>text/plain</content_type>' \ |
482 | '<last_modified>1970-01-01T00:00:01' \ |
483 | '</last_modified></object>' \ |
484 | @@ -822,6 +840,17 @@ |
485 | resp = self.controller.GET(req) |
486 | self.assert_(resp.status_int in (204, 412), resp.status_int) |
487 | |
488 | + def test_params_no_null(self): |
489 | + self.controller.PUT(Request.blank('/sda1/p/a/c', |
490 | + headers={'X-Timestamp': normalize_timestamp(1)}, |
491 | + environ={'REQUEST_METHOD': 'PUT'})) |
492 | + for param in ('delimiter', 'format', 'limit', 'marker', 'path', |
493 | + 'prefix'): |
494 | + req = Request.blank('/sda1/p/a/c?%s=\x00' % param, |
495 | + environ={'REQUEST_METHOD': 'GET'}) |
496 | + resp = self.controller.GET(req) |
497 | + self.assertEquals(resp.status_int, 400) |
498 | + |
499 | |
500 | if __name__ == '__main__': |
501 | unittest.main() |
502 | |
503 | === modified file 'test/unit/proxy/test_server.py' |
504 | --- test/unit/proxy/test_server.py 2011-06-14 22:20:23 +0000 |
505 | +++ test/unit/proxy/test_server.py 2011-06-17 01:04:33 +0000 |
506 | @@ -2111,6 +2111,20 @@ |
507 | exp = 'HTTP/1.1 412' |
508 | self.assertEquals(headers[:len(exp)], exp) |
509 | |
510 | + def test_chunked_put_bad_utf8_null(self): |
511 | + # Check invalid utf-8 |
512 | + (prolis, acc1lis, acc2lis, con2lis, con2lis, obj1lis, obj2lis) = \ |
513 | + _test_sockets |
514 | + sock = connect_tcp(('localhost', prolis.getsockname()[1])) |
515 | + fd = sock.makefile() |
516 | + fd.write('GET /v1/a%00 HTTP/1.1\r\nHost: localhost\r\n' |
517 | + 'Connection: close\r\nX-Auth-Token: t\r\n' |
518 | + 'Content-Length: 0\r\n\r\n') |
519 | + fd.flush() |
520 | + headers = readuntil2crlfs(fd) |
521 | + exp = 'HTTP/1.1 412' |
522 | + self.assertEquals(headers[:len(exp)], exp) |
523 | + |
524 | def test_chunked_put_bad_path_no_controller(self): |
525 | # Check bad path, no controller |
526 | (prolis, acc1lis, acc2lis, con2lis, con2lis, obj1lis, obj2lis) = \ |
Looks good to me