Merge lp:~cjwatson/lazr.restful/text-field-normalize-crlf into lp:lazr.restful

Proposed by Colin Watson
Status: Merged
Merged at revision: 276
Proposed branch: lp:~cjwatson/lazr.restful/text-field-normalize-crlf
Merge into: lp:lazr.restful
Diff against target: 158 lines (+61/-5)
6 files modified
NEWS.rst (+3/-0)
src/lazr/restful/docs/webservice-marshallers.rst (+10/-0)
src/lazr/restful/example/base/interfaces.py (+5/-0)
src/lazr/restful/example/base/root.py (+10/-4)
src/lazr/restful/example/base/tests/collection.txt (+27/-0)
src/lazr/restful/marshallers.py (+6/-1)
To merge this branch: bzr merge lp:~cjwatson/lazr.restful/text-field-normalize-crlf
Reviewer Review Type Date Requested Status
Cristian Gonzalez (community) Approve
Review via email: mp+396428@code.launchpad.net

Commit message

Normalize line breaks in text fields marshalled from a request.

Description of the change

multipart/form-data encoding (now used by lazr.restful.testing.webservice.WebServiceCaller for named POST requests) requires line breaks to be encoded as CRLF. This caused a discrepancy in Launchpad's lib/lp/code/stories/webservice/xx-branchmergeproposal.txt test, and it seems reasonable to normalize this in the specific case of text fields. Convert any line breaks to Unix-style LF there.

To post a comment you must log in.
Revision history for this message
Cristian Gonzalez (cristiangsp) wrote :

Loos good!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS.rst'
--- NEWS.rst 2021-01-04 11:24:14 +0000
+++ NEWS.rst 2021-01-18 11:37:39 +0000
@@ -11,6 +11,9 @@
11allowing robust use of binary arguments on both Python 2 and 311allowing robust use of binary arguments on both Python 2 and 3
12(bug 1116954).12(bug 1116954).
1313
14Normalize line breaks in text fields marshalled from a request to Unix-style
15LF, since ``multipart/form-data`` encoding requires CRLF.
16
140.23.0 (2020-09-28)170.23.0 (2020-09-28)
15===================18===================
1619
1720
=== modified file 'src/lazr/restful/docs/webservice-marshallers.rst'
--- src/lazr/restful/docs/webservice-marshallers.rst 2020-09-07 09:30:30 +0000
+++ src/lazr/restful/docs/webservice-marshallers.rst 2021-01-18 11:37:39 +0000
@@ -26,6 +26,7 @@
26 >>> def pformat_value(value):26 >>> def pformat_value(value):
27 ... """Pretty-format a single value."""27 ... """Pretty-format a single value."""
28 ... if isinstance(value, six.text_type):28 ... if isinstance(value, six.text_type):
29 ... value = value.encode('unicode_escape').decode('ASCII')
29 ... if "'" in value and '"' not in value:30 ... if "'" in value and '"' not in value:
30 ... return '"%s"' % value31 ... return '"%s"' % value
31 ... else:32 ... else:
@@ -450,6 +451,15 @@
450 >>> print(marshaller.marshall_from_request('null'))451 >>> print(marshaller.marshall_from_request('null'))
451 None452 None
452453
454Line breaks coming from the request are normalized to LF.
455
456 >>> pprint_value(marshaller.marshall_from_request('abc\r\n\r\ndef\r\n'))
457 'abc\n\ndef\n'
458 >>> pprint_value(marshaller.marshall_from_request('abc\n\ndef\n'))
459 'abc\n\ndef\n'
460 >>> pprint_value(marshaller.marshall_from_request('abc\r\rdef\r'))
461 'abc\n\ndef\n'
462
453Bytes463Bytes
454-----464-----
455465
456466
=== modified file 'src/lazr/restful/example/base/interfaces.py'
--- src/lazr/restful/example/base/interfaces.py 2020-09-02 22:35:45 +0000
+++ src/lazr/restful/example/base/interfaces.py 2021-01-18 11:37:39 +0000
@@ -238,6 +238,11 @@
238 def getRecipes():238 def getRecipes():
239 """Return the list of recipes."""239 """Return the list of recipes."""
240240
241 @export_factory_operation(
242 IRecipe, ['id', 'cookbook', 'dish', 'instructions', 'private'])
243 def createRecipe(id, cookbook, dish, instructions, private=False):
244 """Create a new recipe."""
245
241 def removeRecipe(recipe):246 def removeRecipe(recipe):
242 """Remove a recipe from the list."""247 """Remove a recipe from the list."""
243248
244249
=== modified file 'src/lazr/restful/example/base/root.py'
--- src/lazr/restful/example/base/root.py 2020-02-04 11:52:59 +0000
+++ src/lazr/restful/example/base/root.py 2021-01-18 11:37:39 +0000
@@ -19,6 +19,7 @@
19from zope.location.interfaces import ILocation19from zope.location.interfaces import ILocation
20from zope.component import getMultiAdapter, getUtility20from zope.component import getMultiAdapter, getUtility
21from zope.schema.interfaces import IBytes21from zope.schema.interfaces import IBytes
22from zope.security.proxy import removeSecurityProxy
2223
23from lazr.restful import directives, ServiceRootResource24from lazr.restful import directives, ServiceRootResource
2425
@@ -180,9 +181,9 @@
180 def __init__(self, id, cookbook, dish, instructions, private=False):181 def __init__(self, id, cookbook, dish, instructions, private=False):
181 self.id = id182 self.id = id
182 self.dish = dish183 self.dish = dish
183 self.dish.recipes.append(self)184 removeSecurityProxy(self.dish.recipes).append(self)
184 self.cookbook = cookbook185 self.cookbook = cookbook
185 self.cookbook.recipes.append(self)186 removeSecurityProxy(self.cookbook.recipes).append(self)
186 self.instructions = instructions187 self.instructions = instructions
187 self.private = private188 self.private = private
188 self.prepared_image = None189 self.prepared_image = None
@@ -321,10 +322,15 @@
321 return match[0]322 return match[0]
322 return None323 return None
323324
325 def createRecipe(self, id, cookbook, dish, instructions, private=False):
326 recipe = Recipe(id, cookbook, dish, instructions, private=private)
327 self.recipes.append(recipe)
328 return recipe
329
324 def removeRecipe(self, recipe):330 def removeRecipe(self, recipe):
325 self.recipes.remove(recipe)331 self.recipes.remove(recipe)
326 recipe.cookbook.removeRecipe(recipe)332 removeSecurityProxy(recipe.cookbook).removeRecipe(recipe)
327 recipe.dish.removeRecipe(recipe)333 removeSecurityProxy(recipe.dish).removeRecipe(recipe)
328334
329335
330# Define some globally accessible sample data.336# Define some globally accessible sample data.
331337
=== modified file 'src/lazr/restful/example/base/tests/collection.txt'
--- src/lazr/restful/example/base/tests/collection.txt 2020-09-07 09:54:10 +0000
+++ src/lazr/restful/example/base/tests/collection.txt 2021-01-18 11:37:39 +0000
@@ -320,3 +320,30 @@
320 HTTP/1.1 400 Bad Request320 HTTP/1.1 400 Bad Request
321 ...321 ...
322 No such operation: nosuchop322 No such operation: nosuchop
323
324POST operations may involve text fields. These are marshalled via a
325multipart/form-data request, which requires line breaks to be represented as
326CRLF. The server turns these into Unix-style LF.
327
328 >>> import six
329
330 >>> def create_recipe(id, cookbook_link, dish_link, instructions,
331 ... private=False):
332 ... return webservice.named_post(
333 ... "/recipes", "createRecipe", {},
334 ... id=id, cookbook=cookbook_link, dish=dish_link,
335 ... instructions=instructions, private=private)
336
337 >>> cookbook_link = webservice.get(
338 ... quote("/cookbooks/The Joy of Cooking")).jsonBody()["self_link"]
339 >>> dish_link = webservice.get(
340 ... quote("/dishes/Roast chicken")).jsonBody()["self_link"]
341 >>> response = create_recipe(
342 ... 20, cookbook_link, dish_link,
343 ... "Recipe\r\ncontaining\rsome\nline\r\n\r\nbreaks")
344 >>> response.status
345 201
346 >>> recipe = webservice.get(response.getHeader("Location")).jsonBody()
347 >>> six.ensure_str(recipe["instructions"])
348 'Recipe\ncontaining\nsome\nline\n\nbreaks'
349 >>> _ = webservice.delete(recipe["self_link"])
323350
=== modified file 'src/lazr/restful/marshallers.py'
--- src/lazr/restful/marshallers.py 2020-09-01 13:20:54 +0000
+++ src/lazr/restful/marshallers.py 2021-01-18 11:37:39 +0000
@@ -26,8 +26,9 @@
2626
27from datetime import datetime27from datetime import datetime
28from io import BytesIO28from io import BytesIO
29import re
30
29import pytz31import pytz
30
31import simplejson32import simplejson
32import six33import six
33from six.moves.urllib.parse import unquote34from six.moves.urllib.parse import unquote
@@ -329,6 +330,10 @@
329 Converts the value to unicode.330 Converts the value to unicode.
330 """331 """
331 value = six.text_type(value)332 value = six.text_type(value)
333 # multipart/form-data encoding of text fields is required (RFC 2046
334 # section 4.1.1) to use CRLF for line breaks. Normalize to
335 # Unix-style LF.
336 value = re.sub(r'\r\n?', '\n', value)
332 return super(TextFieldMarshaller, self)._marshall_from_request(value)337 return super(TextFieldMarshaller, self)._marshall_from_request(value)
333338
334339

Subscribers

People subscribed via source and target branches