Merge lp:~gholt/swift/xmlcontrolchars into lp:~hudson-openstack/swift/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
Reviewer Review Type Date Requested Status
John Dickinson Approve
David Goetz (community) Approve
Review via email: mp+64926@code.launchpad.net

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
317. By gholt

Fixed typo/bug with 20 instead of 0x20

Revision history for this message
David Goetz (david-goetz) wrote :

Looks good to me

review: Approve
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>%s&#x1;test</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>object&#x1;test</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('&#x4;', '\\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('&#x4;', '\\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&#x1;</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&#x2;</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&#x3;</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) = \