Merge lp:~stevenk/launchpad/handle-invalid-unicode-in-query-string-redux into lp:launchpad
- handle-invalid-unicode-in-query-string-redux
- Merge into devel
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Steve Kowalik | ||||||||
Approved revision: | no longer in the source branch. | ||||||||
Merged at revision: | 16482 | ||||||||
Proposed branch: | lp:~stevenk/launchpad/handle-invalid-unicode-in-query-string-redux | ||||||||
Merge into: | lp:launchpad | ||||||||
Diff against target: |
566 lines (+114/-58) 18 files modified
lib/lp/app/browser/tales.py (+1/-1) lib/lp/bugs/interfaces/bug.py (+2/-2) lib/lp/bugs/model/bug.py (+8/-2) lib/lp/registry/interfaces/productrelease.py (+4/-6) lib/lp/registry/model/productrelease.py (+22/-17) lib/lp/registry/stories/webservice/xx-project-registry.txt (+17/-7) lib/lp/registry/subscribers.py (+2/-4) lib/lp/registry/tests/test_subscribers.py (+1/-1) lib/lp/services/webapp/escaping.py (+1/-1) lib/lp/services/webapp/menu.py (+1/-1) lib/lp/services/webapp/pgsession.py (+1/-1) lib/lp/services/webapp/publisher.py (+20/-2) lib/lp/services/webapp/servers.py (+12/-5) lib/lp/services/webapp/tests/test_menu.py (+2/-2) lib/lp/services/webapp/tests/test_servers.py (+15/-3) lib/lp/services/webapp/tests/test_user_requested_oops.py (+2/-1) lib/lp/services/webapp/tests/test_view_model.py (+2/-1) lib/lp/testing/tests/test_publication.py (+1/-1) |
||||||||
To merge this branch: | bzr merge lp:~stevenk/launchpad/handle-invalid-unicode-in-query-string-redux | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+146773@code.launchpad.net |
Commit message
If we fail to decode the query string parameters, forcibly decode it to utf-8 with replacements, again. We also work around a Zope bug by grabbing the request and pulling the un-decoded file contents out for IProductRelease
Description of the change
This branch resurrects the changes from r1646[12] which were reverted due to discovering that lazr.restfulclient does not set filename on file uploads, so Zope will encode the contents, resulting in garbage being stored.
We work around this by grabbing the current request in the two exported methods (IProductReleas
I have also ripped out the export of get_current_
Preview Diff
1 | === modified file 'lib/lp/app/browser/tales.py' | |||
2 | --- lib/lp/app/browser/tales.py 2013-01-30 05:31:20 +0000 | |||
3 | +++ lib/lp/app/browser/tales.py 2013-02-07 01:27:23 +0000 | |||
4 | @@ -19,6 +19,7 @@ | |||
5 | 19 | import urllib | 19 | import urllib |
6 | 20 | 20 | ||
7 | 21 | from lazr.enum import enumerated_type_registry | 21 | from lazr.enum import enumerated_type_registry |
8 | 22 | from lazr.restful.utils import get_current_browser_request | ||
9 | 22 | from lazr.uri import URI | 23 | from lazr.uri import URI |
10 | 23 | import pytz | 24 | import pytz |
11 | 24 | from z3c.ptcompat import ViewPageTemplateFile | 25 | from z3c.ptcompat import ViewPageTemplateFile |
12 | @@ -94,7 +95,6 @@ | |||
13 | 94 | get_facet, | 95 | get_facet, |
14 | 95 | ) | 96 | ) |
15 | 96 | from lp.services.webapp.publisher import ( | 97 | from lp.services.webapp.publisher import ( |
16 | 97 | get_current_browser_request, | ||
17 | 98 | LaunchpadView, | 98 | LaunchpadView, |
18 | 99 | nearest, | 99 | nearest, |
19 | 100 | ) | 100 | ) |
20 | 101 | 101 | ||
21 | === modified file 'lib/lp/bugs/interfaces/bug.py' | |||
22 | --- lib/lp/bugs/interfaces/bug.py 2013-01-07 02:40:55 +0000 | |||
23 | +++ lib/lp/bugs/interfaces/bug.py 2013-02-07 01:27:23 +0000 | |||
24 | @@ -685,14 +685,14 @@ | |||
25 | 685 | class IBugEdit(Interface): | 685 | class IBugEdit(Interface): |
26 | 686 | """IBug attributes that require launchpad.Edit permission.""" | 686 | """IBug attributes that require launchpad.Edit permission.""" |
27 | 687 | 687 | ||
29 | 688 | @call_with(owner=REQUEST_USER) | 688 | @call_with(owner=REQUEST_USER, from_api=True) |
30 | 689 | @operation_parameters( | 689 | @operation_parameters( |
31 | 690 | data=Bytes(constraint=attachment_size_constraint), | 690 | data=Bytes(constraint=attachment_size_constraint), |
32 | 691 | comment=Text(), filename=TextLine(), is_patch=Bool(), | 691 | comment=Text(), filename=TextLine(), is_patch=Bool(), |
33 | 692 | content_type=TextLine(), description=Text()) | 692 | content_type=TextLine(), description=Text()) |
34 | 693 | @export_factory_operation(IBugAttachment, []) | 693 | @export_factory_operation(IBugAttachment, []) |
35 | 694 | def addAttachment(owner, data, comment, filename, is_patch=False, | 694 | def addAttachment(owner, data, comment, filename, is_patch=False, |
37 | 695 | content_type=None, description=None): | 695 | content_type=None, description=None, from_api=False): |
38 | 696 | """Attach a file to this bug. | 696 | """Attach a file to this bug. |
39 | 697 | 697 | ||
40 | 698 | :owner: An IPerson. | 698 | :owner: An IPerson. |
41 | 699 | 699 | ||
42 | === modified file 'lib/lp/bugs/model/bug.py' | |||
43 | --- lib/lp/bugs/model/bug.py 2012-12-20 14:55:13 +0000 | |||
44 | +++ lib/lp/bugs/model/bug.py 2013-02-07 01:27:23 +0000 | |||
45 | @@ -1,4 +1,4 @@ | |||
47 | 1 | # Copyright 2009-2012 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
48 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
49 | 3 | 3 | ||
50 | 4 | """Launchpad bug-related database table classes.""" | 4 | """Launchpad bug-related database table classes.""" |
51 | @@ -226,6 +226,7 @@ | |||
52 | 226 | ) | 226 | ) |
53 | 227 | from lp.services.webapp.authorization import check_permission | 227 | from lp.services.webapp.authorization import check_permission |
54 | 228 | from lp.services.webapp.interfaces import ILaunchBag | 228 | from lp.services.webapp.interfaces import ILaunchBag |
55 | 229 | from lp.services.webapp.publisher import get_raw_form_value_from_current_request | ||
56 | 229 | 230 | ||
57 | 230 | 231 | ||
58 | 231 | def snapshot_bug_params(bug_params): | 232 | def snapshot_bug_params(bug_params): |
59 | @@ -1250,8 +1251,13 @@ | |||
60 | 1250 | bug_watch.destroySelf() | 1251 | bug_watch.destroySelf() |
61 | 1251 | 1252 | ||
62 | 1252 | def addAttachment(self, owner, data, comment, filename, is_patch=False, | 1253 | def addAttachment(self, owner, data, comment, filename, is_patch=False, |
64 | 1253 | content_type=None, description=None): | 1254 | content_type=None, description=None, from_api=False): |
65 | 1254 | """See `IBug`.""" | 1255 | """See `IBug`.""" |
66 | 1256 | # XXX: StevenK 2013-02-06 bug=1116954: We should not need to refetch | ||
67 | 1257 | # the file content from the request, since the passed in one has been | ||
68 | 1258 | # wrongly encoded. | ||
69 | 1259 | if from_api: | ||
70 | 1260 | data = get_raw_form_value_from_current_request('data') | ||
71 | 1255 | if isinstance(data, str): | 1261 | if isinstance(data, str): |
72 | 1256 | filecontent = data | 1262 | filecontent = data |
73 | 1257 | else: | 1263 | else: |
74 | 1258 | 1264 | ||
75 | === modified file 'lib/lp/registry/interfaces/productrelease.py' | |||
76 | --- lib/lp/registry/interfaces/productrelease.py 2013-01-25 05:25:34 +0000 | |||
77 | +++ lib/lp/registry/interfaces/productrelease.py 2013-02-07 01:27:23 +0000 | |||
78 | @@ -224,7 +224,7 @@ | |||
79 | 224 | class IProductReleaseEditRestricted(Interface): | 224 | class IProductReleaseEditRestricted(Interface): |
80 | 225 | """`IProductRelease` properties which require `launchpad.Edit`.""" | 225 | """`IProductRelease` properties which require `launchpad.Edit`.""" |
81 | 226 | 226 | ||
83 | 227 | @call_with(uploader=REQUEST_USER) | 227 | @call_with(uploader=REQUEST_USER, from_api=True) |
84 | 228 | @operation_parameters( | 228 | @operation_parameters( |
85 | 229 | filename=TextLine(), | 229 | filename=TextLine(), |
86 | 230 | signature_filename=TextLine(), | 230 | signature_filename=TextLine(), |
87 | @@ -232,16 +232,14 @@ | |||
88 | 232 | file_content=Bytes(constraint=productrelease_file_size_constraint), | 232 | file_content=Bytes(constraint=productrelease_file_size_constraint), |
89 | 233 | signature_content=Bytes( | 233 | signature_content=Bytes( |
90 | 234 | constraint=productrelease_signature_size_constraint), | 234 | constraint=productrelease_signature_size_constraint), |
95 | 235 | file_type=copy_field(IProductReleaseFile['filetype'], required=False) | 235 | file_type=copy_field(IProductReleaseFile['filetype'], required=False)) |
96 | 236 | ) | 236 | @export_factory_operation(IProductReleaseFile, ['description']) |
93 | 237 | @export_factory_operation( | ||
94 | 238 | IProductReleaseFile, ['description']) | ||
97 | 239 | @export_operation_as('add_file') | 237 | @export_operation_as('add_file') |
98 | 240 | def addReleaseFile(filename, file_content, content_type, | 238 | def addReleaseFile(filename, file_content, content_type, |
99 | 241 | uploader, signature_filename=None, | 239 | uploader, signature_filename=None, |
100 | 242 | signature_content=None, | 240 | signature_content=None, |
101 | 243 | file_type=UpstreamFileType.CODETARBALL, | 241 | file_type=UpstreamFileType.CODETARBALL, |
103 | 244 | description=None): | 242 | description=None, from_api=False): |
104 | 245 | """Add file to the library and link to this `IProductRelease`. | 243 | """Add file to the library and link to this `IProductRelease`. |
105 | 246 | 244 | ||
106 | 247 | The signature file will also be added if available. | 245 | The signature file will also be added if available. |
107 | 248 | 246 | ||
108 | === modified file 'lib/lp/registry/model/productrelease.py' | |||
109 | --- lib/lp/registry/model/productrelease.py 2013-01-25 05:25:34 +0000 | |||
110 | +++ lib/lp/registry/model/productrelease.py 2013-02-07 01:27:23 +0000 | |||
111 | @@ -1,4 +1,4 @@ | |||
113 | 1 | # Copyright 2009-2012 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
114 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
115 | 3 | 3 | ||
116 | 4 | __metaclass__ = type | 4 | __metaclass__ = type |
117 | @@ -56,6 +56,7 @@ | |||
118 | 56 | ) | 56 | ) |
119 | 57 | from lp.services.librarian.interfaces import ILibraryFileAliasSet | 57 | from lp.services.librarian.interfaces import ILibraryFileAliasSet |
120 | 58 | from lp.services.propertycache import cachedproperty | 58 | from lp.services.propertycache import cachedproperty |
121 | 59 | from lp.services.webapp.publisher import get_raw_form_value_from_current_request | ||
122 | 59 | 60 | ||
123 | 60 | 61 | ||
124 | 61 | class ProductRelease(SQLBase): | 62 | class ProductRelease(SQLBase): |
125 | @@ -148,7 +149,7 @@ | |||
126 | 148 | uploader, signature_filename=None, | 149 | uploader, signature_filename=None, |
127 | 149 | signature_content=None, | 150 | signature_content=None, |
128 | 150 | file_type=UpstreamFileType.CODETARBALL, | 151 | file_type=UpstreamFileType.CODETARBALL, |
130 | 151 | description=None): | 152 | description=None, from_api=False): |
131 | 152 | """See `IProductRelease`.""" | 153 | """See `IProductRelease`.""" |
132 | 153 | if not self.can_have_release_files: | 154 | if not self.can_have_release_files: |
133 | 154 | raise ProprietaryProduct( | 155 | raise ProprietaryProduct( |
134 | @@ -157,31 +158,35 @@ | |||
135 | 157 | raise InvalidFilename | 158 | raise InvalidFilename |
136 | 158 | # Create the alias for the file. | 159 | # Create the alias for the file. |
137 | 159 | filename = self.normalizeFilename(filename) | 160 | filename = self.normalizeFilename(filename) |
138 | 161 | # XXX: StevenK 2013-02-06 bug=1116954: We should not need to refetch | ||
139 | 162 | # the file content from the request, since the passed in one has been | ||
140 | 163 | # wrongly encoded. | ||
141 | 164 | if from_api: | ||
142 | 165 | file_content = get_raw_form_value_from_current_request( | ||
143 | 166 | 'file_content') | ||
144 | 160 | file_obj, file_size = self._getFileObjectAndSize(file_content) | 167 | file_obj, file_size = self._getFileObjectAndSize(file_content) |
145 | 161 | 168 | ||
146 | 162 | alias = getUtility(ILibraryFileAliasSet).create( | 169 | alias = getUtility(ILibraryFileAliasSet).create( |
150 | 163 | name=filename, | 170 | name=filename, size=file_size, file=file_obj, |
148 | 164 | size=file_size, | ||
149 | 165 | file=file_obj, | ||
151 | 166 | contentType=content_type) | 171 | contentType=content_type) |
152 | 167 | if signature_filename is not None and signature_content is not None: | 172 | if signature_filename is not None and signature_content is not None: |
153 | 173 | # XXX: StevenK 2013-02-06 bug=1116954: We should not need to | ||
154 | 174 | # refetch the file content from the request, since the passed in | ||
155 | 175 | # one has been wrongly encoded. | ||
156 | 176 | if from_api: | ||
157 | 177 | signature_content = get_raw_form_value_from_current_request( | ||
158 | 178 | 'signature_content') | ||
159 | 168 | signature_obj, signature_size = self._getFileObjectAndSize( | 179 | signature_obj, signature_size = self._getFileObjectAndSize( |
160 | 169 | signature_content) | 180 | signature_content) |
163 | 170 | signature_filename = self.normalizeFilename( | 181 | signature_filename = self.normalizeFilename(signature_filename) |
162 | 171 | signature_filename) | ||
164 | 172 | signature_alias = getUtility(ILibraryFileAliasSet).create( | 182 | signature_alias = getUtility(ILibraryFileAliasSet).create( |
169 | 173 | name=signature_filename, | 183 | name=signature_filename, size=signature_size, |
170 | 174 | size=signature_size, | 184 | file=signature_obj, contentType='application/pgp-signature') |
167 | 175 | file=signature_obj, | ||
168 | 176 | contentType='application/pgp-signature') | ||
171 | 177 | else: | 185 | else: |
172 | 178 | signature_alias = None | 186 | signature_alias = None |
179 | 179 | return ProductReleaseFile(productrelease=self, | 187 | return ProductReleaseFile( |
180 | 180 | libraryfile=alias, | 188 | productrelease=self, libraryfile=alias, signature=signature_alias, |
181 | 181 | signature=signature_alias, | 189 | filetype=file_type, description=description, uploader=uploader) |
176 | 182 | filetype=file_type, | ||
177 | 183 | description=description, | ||
178 | 184 | uploader=uploader) | ||
182 | 185 | 190 | ||
183 | 186 | def getFileAliasByName(self, name): | 191 | def getFileAliasByName(self, name): |
184 | 187 | """See `IProductRelease`.""" | 192 | """See `IProductRelease`.""" |
185 | 188 | 193 | ||
186 | === modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt' | |||
187 | --- lib/lp/registry/stories/webservice/xx-project-registry.txt 2012-12-10 13:43:47 +0000 | |||
188 | +++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2013-02-07 01:27:23 +0000 | |||
189 | @@ -1020,8 +1020,8 @@ | |||
190 | 1020 | 1020 | ||
191 | 1021 | >>> pr_url = '/firefox/1.0/1.0.0' | 1021 | >>> pr_url = '/firefox/1.0/1.0.0' |
192 | 1022 | >>> ff_100 = webservice.get(pr_url).jsonBody() | 1022 | >>> ff_100 = webservice.get(pr_url).jsonBody() |
195 | 1023 | >>> file_content="first attachment file content" | 1023 | >>> file_content="first attachment file content \xff" |
196 | 1024 | >>> sig_file_content="hash hash hash" | 1024 | >>> sig_file_content="hash hash hash \xff" |
197 | 1025 | >>> response = webservice.named_post(ff_100['self_link'], 'add_file', | 1025 | >>> response = webservice.named_post(ff_100['self_link'], 'add_file', |
198 | 1026 | ... filename='filename.txt', | 1026 | ... filename='filename.txt', |
199 | 1027 | ... file_content=file_content, | 1027 | ... file_content=file_content, |
200 | @@ -1043,6 +1043,20 @@ | |||
201 | 1043 | >>> print_self_link_of_entries(ff_100_files) | 1043 | >>> print_self_link_of_entries(ff_100_files) |
202 | 1044 | http://.../firefox/1.0/1.0.0/+file/filename.txt | 1044 | http://.../firefox/1.0/1.0.0/+file/filename.txt |
203 | 1045 | 1045 | ||
204 | 1046 | And it has been uploaded correctly. | ||
205 | 1047 | |||
206 | 1048 | >>> from zope.component import getUtility | ||
207 | 1049 | >>> from lp.registry.interfaces.product import IProductSet | ||
208 | 1050 | >>> from lp.testing import login, logout | ||
209 | 1051 | >>> login('bac@canonical.com') | ||
210 | 1052 | >>> concrete_one_zero = getUtility(IProductSet)['firefox'].getRelease( | ||
211 | 1053 | ... '1.0.0') | ||
212 | 1054 | >>> concrete_one_zero.files[0].libraryfile.read() == file_content | ||
213 | 1055 | True | ||
214 | 1056 | >>> concrete_one_zero.files[0].signature.read() == sig_file_content | ||
215 | 1057 | True | ||
216 | 1058 | >>> logout() | ||
217 | 1059 | |||
218 | 1046 | The file type and description are optional. If no signature is | 1060 | The file type and description are optional. If no signature is |
219 | 1047 | available then it must be explicitly set to None. | 1061 | available then it must be explicitly set to None. |
220 | 1048 | 1062 | ||
221 | @@ -1106,12 +1120,8 @@ | |||
222 | 1106 | If a project has a commercial-use subscription then it can be retrieved | 1120 | If a project has a commercial-use subscription then it can be retrieved |
223 | 1107 | through the API. | 1121 | through the API. |
224 | 1108 | 1122 | ||
225 | 1109 | >>> from zope.component import getUtility | ||
226 | 1110 | >>> from lp.registry.interfaces.product import IProductSet | ||
227 | 1111 | >>> from lp.testing import login, logout | ||
228 | 1112 | >>> login('bac@canonical.com') | 1123 | >>> login('bac@canonical.com') |
231 | 1113 | >>> product_set = getUtility(IProductSet) | 1124 | >>> mmm = getUtility(IProductSet)['mega-money-maker'] |
230 | 1114 | >>> mmm = product_set.getByName('mega-money-maker') | ||
232 | 1115 | >>> print mmm.commercial_subscription | 1125 | >>> print mmm.commercial_subscription |
233 | 1116 | None | 1126 | None |
234 | 1117 | 1127 | ||
235 | 1118 | 1128 | ||
236 | === modified file 'lib/lp/registry/subscribers.py' | |||
237 | --- lib/lp/registry/subscribers.py 2012-11-26 08:40:20 +0000 | |||
238 | +++ lib/lp/registry/subscribers.py 2013-02-07 01:27:23 +0000 | |||
239 | @@ -11,6 +11,7 @@ | |||
240 | 11 | from datetime import datetime | 11 | from datetime import datetime |
241 | 12 | import textwrap | 12 | import textwrap |
242 | 13 | 13 | ||
243 | 14 | from lazr.restful.utils import get_current_browser_request | ||
244 | 14 | import pytz | 15 | import pytz |
245 | 15 | from zope.security.proxy import removeSecurityProxy | 16 | from zope.security.proxy import removeSecurityProxy |
246 | 16 | 17 | ||
247 | @@ -23,10 +24,7 @@ | |||
248 | 23 | simple_sendmail, | 24 | simple_sendmail, |
249 | 24 | ) | 25 | ) |
250 | 25 | from lp.services.webapp.escaping import structured | 26 | from lp.services.webapp.escaping import structured |
255 | 26 | from lp.services.webapp.publisher import ( | 27 | from lp.services.webapp.publisher import canonical_url |
252 | 27 | canonical_url, | ||
253 | 28 | get_current_browser_request, | ||
254 | 29 | ) | ||
256 | 30 | 28 | ||
257 | 31 | 29 | ||
258 | 32 | def product_licenses_modified(product, event): | 30 | def product_licenses_modified(product, event): |
259 | 33 | 31 | ||
260 | === modified file 'lib/lp/registry/tests/test_subscribers.py' | |||
261 | --- lib/lp/registry/tests/test_subscribers.py 2012-12-10 13:43:47 +0000 | |||
262 | +++ lib/lp/registry/tests/test_subscribers.py 2013-02-07 01:27:23 +0000 | |||
263 | @@ -7,6 +7,7 @@ | |||
264 | 7 | 7 | ||
265 | 8 | from datetime import datetime | 8 | from datetime import datetime |
266 | 9 | 9 | ||
267 | 10 | from lazr.restful.utils import get_current_browser_request | ||
268 | 10 | import pytz | 11 | import pytz |
269 | 11 | from zope.component import getUtility | 12 | from zope.component import getUtility |
270 | 12 | from zope.security.proxy import removeSecurityProxy | 13 | from zope.security.proxy import removeSecurityProxy |
271 | @@ -23,7 +24,6 @@ | |||
272 | 23 | product_licenses_modified, | 24 | product_licenses_modified, |
273 | 24 | ) | 25 | ) |
274 | 25 | from lp.services.webapp.escaping import html_escape | 26 | from lp.services.webapp.escaping import html_escape |
275 | 26 | from lp.services.webapp.publisher import get_current_browser_request | ||
276 | 27 | from lp.testing import ( | 27 | from lp.testing import ( |
277 | 28 | login_person, | 28 | login_person, |
278 | 29 | logout, | 29 | logout, |
279 | 30 | 30 | ||
280 | === modified file 'lib/lp/services/webapp/escaping.py' | |||
281 | --- lib/lp/services/webapp/escaping.py 2012-12-10 22:25:44 +0000 | |||
282 | +++ lib/lp/services/webapp/escaping.py 2013-02-07 01:27:23 +0000 | |||
283 | @@ -8,6 +8,7 @@ | |||
284 | 8 | 'structured', | 8 | 'structured', |
285 | 9 | ] | 9 | ] |
286 | 10 | 10 | ||
287 | 11 | from lazr.restful.utils import get_current_browser_request | ||
288 | 11 | from zope.i18n import ( | 12 | from zope.i18n import ( |
289 | 12 | Message, | 13 | Message, |
290 | 13 | translate, | 14 | translate, |
291 | @@ -15,7 +16,6 @@ | |||
292 | 15 | from zope.interface import implements | 16 | from zope.interface import implements |
293 | 16 | 17 | ||
294 | 17 | from lp.services.webapp.interfaces import IStructuredString | 18 | from lp.services.webapp.interfaces import IStructuredString |
295 | 18 | from lp.services.webapp.publisher import get_current_browser_request | ||
296 | 19 | 19 | ||
297 | 20 | 20 | ||
298 | 21 | HTML_REPLACEMENTS = ( | 21 | HTML_REPLACEMENTS = ( |
299 | 22 | 22 | ||
300 | === modified file 'lib/lp/services/webapp/menu.py' | |||
301 | --- lib/lp/services/webapp/menu.py 2012-12-12 04:59:52 +0000 | |||
302 | +++ lib/lp/services/webapp/menu.py 2013-02-07 01:27:23 +0000 | |||
303 | @@ -21,6 +21,7 @@ | |||
304 | 21 | import types | 21 | import types |
305 | 22 | 22 | ||
306 | 23 | from lazr.delegates import delegates | 23 | from lazr.delegates import delegates |
307 | 24 | from lazr.restful.utils import get_current_browser_request | ||
308 | 24 | from lazr.uri import ( | 25 | from lazr.uri import ( |
309 | 25 | InvalidURIError, | 26 | InvalidURIError, |
310 | 26 | URI, | 27 | URI, |
311 | @@ -45,7 +46,6 @@ | |||
312 | 45 | ) | 46 | ) |
313 | 46 | from lp.services.webapp.publisher import ( | 47 | from lp.services.webapp.publisher import ( |
314 | 47 | canonical_url, | 48 | canonical_url, |
315 | 48 | get_current_browser_request, | ||
316 | 49 | LaunchpadView, | 49 | LaunchpadView, |
317 | 50 | UserAttributeCache, | 50 | UserAttributeCache, |
318 | 51 | ) | 51 | ) |
319 | 52 | 52 | ||
320 | === modified file 'lib/lp/services/webapp/pgsession.py' | |||
321 | --- lib/lp/services/webapp/pgsession.py 2012-01-01 03:00:09 +0000 | |||
322 | +++ lib/lp/services/webapp/pgsession.py 2013-02-07 01:27:23 +0000 | |||
323 | @@ -9,6 +9,7 @@ | |||
324 | 9 | import time | 9 | import time |
325 | 10 | from UserDict import DictMixin | 10 | from UserDict import DictMixin |
326 | 11 | 11 | ||
327 | 12 | from lazr.restful.utils import get_current_browser_request | ||
328 | 12 | from storm.zope.interfaces import IZStorm | 13 | from storm.zope.interfaces import IZStorm |
329 | 13 | from zope.app.security.interfaces import IUnauthenticatedPrincipal | 14 | from zope.app.security.interfaces import IUnauthenticatedPrincipal |
330 | 14 | from zope.component import getUtility | 15 | from zope.component import getUtility |
331 | @@ -21,7 +22,6 @@ | |||
332 | 21 | ) | 22 | ) |
333 | 22 | 23 | ||
334 | 23 | from lp.services.helpers import ensure_unicode | 24 | from lp.services.helpers import ensure_unicode |
335 | 24 | from lp.services.webapp.publisher import get_current_browser_request | ||
336 | 25 | 25 | ||
337 | 26 | 26 | ||
338 | 27 | SECONDS = 1 | 27 | SECONDS = 1 |
339 | 28 | 28 | ||
340 | === modified file 'lib/lp/services/webapp/publisher.py' | |||
341 | --- lib/lp/services/webapp/publisher.py 2012-11-26 18:44:34 +0000 | |||
342 | +++ lib/lp/services/webapp/publisher.py 2013-02-07 01:27:23 +0000 | |||
343 | @@ -1,4 +1,4 @@ | |||
345 | 1 | # Copyright 2009-2012 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
346 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
347 | 3 | 3 | ||
348 | 4 | """Publisher of objects as web pages. | 4 | """Publisher of objects as web pages. |
349 | @@ -8,13 +8,13 @@ | |||
350 | 8 | __metaclass__ = type | 8 | __metaclass__ = type |
351 | 9 | __all__ = [ | 9 | __all__ = [ |
352 | 10 | 'DataDownloadView', | 10 | 'DataDownloadView', |
353 | 11 | 'get_raw_form_value_from_current_request', | ||
354 | 11 | 'LaunchpadContainer', | 12 | 'LaunchpadContainer', |
355 | 12 | 'LaunchpadView', | 13 | 'LaunchpadView', |
356 | 13 | 'LaunchpadXMLRPCView', | 14 | 'LaunchpadXMLRPCView', |
357 | 14 | 'canonical_name', | 15 | 'canonical_name', |
358 | 15 | 'canonical_url', | 16 | 'canonical_url', |
359 | 16 | 'canonical_url_iterator', | 17 | 'canonical_url_iterator', |
360 | 17 | 'get_current_browser_request', | ||
361 | 18 | 'nearest', | 18 | 'nearest', |
362 | 19 | 'Navigation', | 19 | 'Navigation', |
363 | 20 | 'rootObject', | 20 | 'rootObject', |
364 | @@ -26,6 +26,7 @@ | |||
365 | 26 | 'UserAttributeCache', | 26 | 'UserAttributeCache', |
366 | 27 | ] | 27 | ] |
367 | 28 | 28 | ||
368 | 29 | from cgi import FieldStorage | ||
369 | 29 | import httplib | 30 | import httplib |
370 | 30 | import re | 31 | import re |
371 | 31 | 32 | ||
372 | @@ -800,6 +801,23 @@ | |||
373 | 800 | return None | 801 | return None |
374 | 801 | 802 | ||
375 | 802 | 803 | ||
376 | 804 | def get_raw_form_value_from_current_request(field_name): | ||
377 | 805 | # XXX: StevenK 2013-02-06 bug=1116954: We should not need to refetch | ||
378 | 806 | # the file content from the request, since the passed in one has been | ||
379 | 807 | # wrongly encoded. | ||
380 | 808 | # Circular imports. | ||
381 | 809 | from lp.services.webapp.servers import WebServiceClientRequest | ||
382 | 810 | request = get_current_browser_request() | ||
383 | 811 | assert isinstance(request, WebServiceClientRequest) | ||
384 | 812 | # Zope wrongly encodes any form element that doesn't look like a file, | ||
385 | 813 | # so re-fetch the file content if it has been encoded. | ||
386 | 814 | if request and request.form.has_key(field_name) and isinstance( | ||
387 | 815 | request.form[field_name], unicode): | ||
388 | 816 | request._environ['wsgi.input'].seek(0) | ||
389 | 817 | fs = FieldStorage(fp=request._body_instream, environ=request._environ) | ||
390 | 818 | return fs[field_name].value | ||
391 | 819 | |||
392 | 820 | |||
393 | 803 | class RootObject: | 821 | class RootObject: |
394 | 804 | implements(ILaunchpadApplication, ILaunchpadRoot) | 822 | implements(ILaunchpadApplication, ILaunchpadRoot) |
395 | 805 | 823 | ||
396 | 806 | 824 | ||
397 | === modified file 'lib/lp/services/webapp/servers.py' | |||
398 | --- lib/lp/services/webapp/servers.py 2013-02-03 01:49:37 +0000 | |||
399 | +++ lib/lp/services/webapp/servers.py 2013-02-07 01:27:23 +0000 | |||
400 | @@ -1,4 +1,4 @@ | |||
402 | 1 | # Copyright 2009-2012 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
403 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
404 | 3 | 3 | ||
405 | 4 | """Definition of the internet servers that Launchpad uses.""" | 4 | """Definition of the internet servers that Launchpad uses.""" |
406 | @@ -18,6 +18,7 @@ | |||
407 | 18 | WebServicePublicationMixin, | 18 | WebServicePublicationMixin, |
408 | 19 | WebServiceRequestTraversal, | 19 | WebServiceRequestTraversal, |
409 | 20 | ) | 20 | ) |
410 | 21 | from lazr.restful.utils import get_current_browser_request | ||
411 | 21 | from lazr.uri import URI | 22 | from lazr.uri import URI |
412 | 22 | import transaction | 23 | import transaction |
413 | 23 | from transaction.interfaces import ISynchronizer | 24 | from transaction.interfaces import ISynchronizer |
414 | @@ -100,10 +101,7 @@ | |||
415 | 100 | ) | 101 | ) |
416 | 101 | from lp.services.webapp.opstats import OpStats | 102 | from lp.services.webapp.opstats import OpStats |
417 | 102 | from lp.services.webapp.publication import LaunchpadBrowserPublication | 103 | from lp.services.webapp.publication import LaunchpadBrowserPublication |
422 | 103 | from lp.services.webapp.publisher import ( | 104 | from lp.services.webapp.publisher import RedirectionView |
419 | 104 | get_current_browser_request, | ||
420 | 105 | RedirectionView, | ||
421 | 106 | ) | ||
423 | 107 | from lp.services.webapp.vhosts import allvhosts | 105 | from lp.services.webapp.vhosts import allvhosts |
424 | 108 | from lp.services.webservice.interfaces import IWebServiceApplication | 106 | from lp.services.webservice.interfaces import IWebServiceApplication |
425 | 109 | from lp.testopenid.interfaces.server import ITestOpenIDApplication | 107 | from lp.testopenid.interfaces.server import ITestOpenIDApplication |
426 | @@ -657,6 +655,15 @@ | |||
427 | 657 | """As per zope.publisher.browser.BrowserRequest._createResponse""" | 655 | """As per zope.publisher.browser.BrowserRequest._createResponse""" |
428 | 658 | return LaunchpadBrowserResponse() | 656 | return LaunchpadBrowserResponse() |
429 | 659 | 657 | ||
430 | 658 | def _decode(self, text): | ||
431 | 659 | text = super(LaunchpadBrowserRequest, self)._decode(text) | ||
432 | 660 | if isinstance(text, str): | ||
433 | 661 | # BrowserRequest._decode failed to do so with the user-specified | ||
434 | 662 | # charsets, so decode as UTF-8 with replacements, since we always | ||
435 | 663 | # want unicode. | ||
436 | 664 | text = unicode(text, 'utf-8', 'replace') | ||
437 | 665 | return text | ||
438 | 666 | |||
439 | 660 | @cachedproperty | 667 | @cachedproperty |
440 | 661 | def form_ng(self): | 668 | def form_ng(self): |
441 | 662 | """See ILaunchpadBrowserApplicationRequest.""" | 669 | """See ILaunchpadBrowserApplicationRequest.""" |
442 | 663 | 670 | ||
443 | === modified file 'lib/lp/services/webapp/tests/test_menu.py' | |||
444 | --- lib/lp/services/webapp/tests/test_menu.py 2012-01-01 02:58:52 +0000 | |||
445 | +++ lib/lp/services/webapp/tests/test_menu.py 2013-02-07 01:27:23 +0000 | |||
446 | @@ -3,6 +3,7 @@ | |||
447 | 3 | 3 | ||
448 | 4 | __metaclass__ = type | 4 | __metaclass__ = type |
449 | 5 | 5 | ||
450 | 6 | from lazr.restful.utils import get_current_browser_request | ||
451 | 6 | from zope.security.management import newInteraction | 7 | from zope.security.management import newInteraction |
452 | 7 | 8 | ||
453 | 8 | from lp.services.webapp.menu import ( | 9 | from lp.services.webapp.menu import ( |
454 | @@ -10,7 +11,6 @@ | |||
455 | 10 | MENU_ANNOTATION_KEY, | 11 | MENU_ANNOTATION_KEY, |
456 | 11 | MenuBase, | 12 | MenuBase, |
457 | 12 | ) | 13 | ) |
458 | 13 | from lp.services.webapp.publisher import get_current_browser_request | ||
459 | 14 | from lp.testing import ( | 14 | from lp.testing import ( |
460 | 15 | ANONYMOUS, | 15 | ANONYMOUS, |
461 | 16 | login, | 16 | login, |
462 | @@ -77,7 +77,7 @@ | |||
463 | 77 | login(ANONYMOUS) | 77 | login(ANONYMOUS) |
464 | 78 | context = object() | 78 | context = object() |
465 | 79 | menu = TestMenu(context) | 79 | menu = TestMenu(context) |
467 | 80 | link = menu._get_link('test_link') | 80 | menu._get_link('test_link') |
468 | 81 | cache = get_current_browser_request().annotations.get( | 81 | cache = get_current_browser_request().annotations.get( |
469 | 82 | MENU_ANNOTATION_KEY) | 82 | MENU_ANNOTATION_KEY) |
470 | 83 | self.assertEquals(len(cache.keys()), 1) | 83 | self.assertEquals(len(cache.keys()), 1) |
471 | 84 | 84 | ||
472 | === modified file 'lib/lp/services/webapp/tests/test_servers.py' | |||
473 | --- lib/lp/services/webapp/tests/test_servers.py 2013-02-03 01:49:37 +0000 | |||
474 | +++ lib/lp/services/webapp/tests/test_servers.py 2013-02-07 01:27:23 +0000 | |||
475 | @@ -1,4 +1,4 @@ | |||
477 | 1 | # Copyright 2009-2012 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
478 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
479 | 3 | 3 | ||
480 | 4 | __metaclass__ = type | 4 | __metaclass__ = type |
481 | @@ -50,6 +50,7 @@ | |||
482 | 50 | EventRecorder, | 50 | EventRecorder, |
483 | 51 | TestCase, | 51 | TestCase, |
484 | 52 | ) | 52 | ) |
485 | 53 | from lp.testing.layers import FunctionalLayer | ||
486 | 53 | 54 | ||
487 | 54 | 55 | ||
488 | 55 | class SetInWSGIEnvironmentTestCase(TestCase): | 56 | class SetInWSGIEnvironmentTestCase(TestCase): |
489 | @@ -230,8 +231,7 @@ | |||
490 | 230 | 231 | ||
491 | 231 | for method in denied_methods: | 232 | for method in denied_methods: |
492 | 232 | env = self.wsgi_env(self.non_api_path, method) | 233 | env = self.wsgi_env(self.non_api_path, method) |
495 | 233 | self.assert_(self.factory.canHandle(env), | 234 | self.assert_(self.factory.canHandle(env), "Sanity check") |
494 | 234 | "Sanity check") | ||
496 | 235 | # Returns a tuple of (request_factory, publication_factory). | 235 | # Returns a tuple of (request_factory, publication_factory). |
497 | 236 | rfactory, pfactory = self.factory.checkRequest(env) | 236 | rfactory, pfactory = self.factory.checkRequest(env) |
498 | 237 | self.assert_(rfactory is not None, | 237 | self.assert_(rfactory is not None, |
499 | @@ -352,6 +352,8 @@ | |||
500 | 352 | class TestBasicLaunchpadRequest(TestCase): | 352 | class TestBasicLaunchpadRequest(TestCase): |
501 | 353 | """Tests for the base request class""" | 353 | """Tests for the base request class""" |
502 | 354 | 354 | ||
503 | 355 | layer = FunctionalLayer | ||
504 | 356 | |||
505 | 355 | def test_baserequest_response_should_vary(self): | 357 | def test_baserequest_response_should_vary(self): |
506 | 356 | """Test that our base response has a proper vary header.""" | 358 | """Test that our base response has a proper vary header.""" |
507 | 357 | request = LaunchpadBrowserRequest(StringIO.StringIO(''), {}) | 359 | request = LaunchpadBrowserRequest(StringIO.StringIO(''), {}) |
508 | @@ -387,6 +389,16 @@ | |||
509 | 387 | request = LaunchpadBrowserRequest(StringIO.StringIO(''), env) | 389 | request = LaunchpadBrowserRequest(StringIO.StringIO(''), env) |
510 | 388 | self.assertEquals(u'fnord/trunk\ufffd', request.getHeader('PATH_INFO')) | 390 | self.assertEquals(u'fnord/trunk\ufffd', request.getHeader('PATH_INFO')) |
511 | 389 | 391 | ||
512 | 392 | def test_request_with_invalid_query_string_recovers(self): | ||
513 | 393 | # When the query string has invalid utf-8, it is decoded with | ||
514 | 394 | # replacement. | ||
515 | 395 | env = {'QUERY_STRING': 'field.title=subproc\xe9s '} | ||
516 | 396 | request = LaunchpadBrowserRequest(StringIO.StringIO(''), env) | ||
517 | 397 | # XXX: Python 2.6 and 2.7 handle unicode replacement differently. | ||
518 | 398 | self.assertIn( | ||
519 | 399 | request.query_string_params['field.title'], | ||
520 | 400 | ([u'subproc\ufffd'], [u'subproc\ufffds '])) | ||
521 | 401 | |||
522 | 390 | 402 | ||
523 | 391 | class TestFeedsBrowserRequest(TestCase): | 403 | class TestFeedsBrowserRequest(TestCase): |
524 | 392 | """Tests for `FeedsBrowserRequest`.""" | 404 | """Tests for `FeedsBrowserRequest`.""" |
525 | 393 | 405 | ||
526 | === modified file 'lib/lp/services/webapp/tests/test_user_requested_oops.py' | |||
527 | --- lib/lp/services/webapp/tests/test_user_requested_oops.py 2012-03-06 23:39:08 +0000 | |||
528 | +++ lib/lp/services/webapp/tests/test_user_requested_oops.py 2013-02-07 01:27:23 +0000 | |||
529 | @@ -1,4 +1,5 @@ | |||
531 | 1 | # Copyright 2009 Canonical Ltd. All rights reserved. | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the |
532 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
533 | 2 | 3 | ||
534 | 3 | """Tests for the user requested oops using ++oops++ traversal.""" | 4 | """Tests for the user requested oops using ++oops++ traversal.""" |
535 | 4 | 5 | ||
536 | 5 | 6 | ||
537 | === modified file 'lib/lp/services/webapp/tests/test_view_model.py' | |||
538 | --- lib/lp/services/webapp/tests/test_view_model.py 2012-01-01 02:58:52 +0000 | |||
539 | +++ lib/lp/services/webapp/tests/test_view_model.py 2013-02-07 01:27:23 +0000 | |||
540 | @@ -1,4 +1,5 @@ | |||
542 | 1 | # Copyright 2011 Canonical Ltd. All rights reserved. | 1 | # Copyright 2011 Canonical Ltd. This software is licensed under the |
543 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
544 | 2 | 3 | ||
545 | 3 | """Tests for the user requested oops using ++oops++ traversal.""" | 4 | """Tests for the user requested oops using ++oops++ traversal.""" |
546 | 4 | 5 | ||
547 | 5 | 6 | ||
548 | === modified file 'lib/lp/testing/tests/test_publication.py' | |||
549 | --- lib/lp/testing/tests/test_publication.py 2012-04-12 06:06:49 +0000 | |||
550 | +++ lib/lp/testing/tests/test_publication.py 2013-02-07 01:27:23 +0000 | |||
551 | @@ -6,6 +6,7 @@ | |||
552 | 6 | __metaclass__ = type | 6 | __metaclass__ = type |
553 | 7 | 7 | ||
554 | 8 | from lazr.restful import EntryResource | 8 | from lazr.restful import EntryResource |
555 | 9 | from lazr.restful.utils import get_current_browser_request | ||
556 | 9 | from zope.app.pagetemplate.simpleviewclass import simple | 10 | from zope.app.pagetemplate.simpleviewclass import simple |
557 | 10 | from zope.component import ( | 11 | from zope.component import ( |
558 | 11 | getSiteManager, | 12 | getSiteManager, |
559 | @@ -24,7 +25,6 @@ | |||
560 | 24 | ILaunchpadRoot, | 25 | ILaunchpadRoot, |
561 | 25 | ) | 26 | ) |
562 | 26 | from lp.services.webapp.publisher import ( | 27 | from lp.services.webapp.publisher import ( |
563 | 27 | get_current_browser_request, | ||
564 | 28 | Navigation, | 28 | Navigation, |
565 | 29 | stepthrough, | 29 | stepthrough, |
566 | 30 | ) | 30 | ) |
You'll want to also apply the hack to signature_content, but otherwise fine.