launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #31810
[Merge] ~goulinkh/launchpad:goulin-escape-query-params into launchpad:master
Goulin Khoge has proposed merging ~goulinkh/launchpad:goulin-escape-query-params into launchpad:master.
Commit message:
Escape query param values before rendering the HTML templates.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~goulinkh/launchpad/+git/launchpad/+merge/476203
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~goulinkh/launchpad:goulin-escape-query-params into launchpad:master.
diff --git a/lib/lp/services/webapp/publisher.py b/lib/lp/services/webapp/publisher.py
index 18aca49..64892eb 100644
--- a/lib/lp/services/webapp/publisher.py
+++ b/lib/lp/services/webapp/publisher.py
@@ -306,6 +306,7 @@ class LaunchpadView(UserAttributeCache):
cache = self._get_json_cache()
if cache is None:
return
+ self.form = request.form_ng
related_features = cache.setdefault("related_features", {})
related_features.update(self.related_feature_info)
diff --git a/lib/lp/services/webapp/servers.py b/lib/lp/services/webapp/servers.py
index 1bed9ee..d520286 100644
--- a/lib/lp/services/webapp/servers.py
+++ b/lib/lp/services/webapp/servers.py
@@ -69,6 +69,7 @@ from lp.services.webapp.authorization import (
)
from lp.services.webapp.errorlog import ErrorReportRequest
from lp.services.webapp.interaction import get_interaction_extras
+from lp.services.webapp.escaping import html_escape
from lp.services.webapp.interfaces import (
IBasicLaunchpadRequest,
IBrowserFormNG,
@@ -748,6 +749,10 @@ class BrowserFormNG:
def __init__(self, form):
"""Create a new BrowserFormNG that wraps a dict of form data."""
self.form = form
+ # sanitize the form data
+ for key, value in list(self.form.items()):
+ if isinstance(value, str):
+ self.form[key] = html_escape(value)
def __contains__(self, name):
"""See IBrowserFormNG."""
diff --git a/lib/lp/services/webapp/tests/test_publisher.py b/lib/lp/services/webapp/tests/test_publisher.py
index 8e6cc93..0f1051d 100644
--- a/lib/lp/services/webapp/tests/test_publisher.py
+++ b/lib/lp/services/webapp/tests/test_publisher.py
@@ -526,6 +526,63 @@ class TestLaunchpadView(TestCaseWithFactory):
self.assertEqual(expected_beta_features, view.beta_features)
+ def test_request_form_sanitizes_html(self):
+ """Test that HTML in form parameters is properly escaped."""
+ request = LaunchpadTestRequest(
+ form={'resize_frame': '<script>alert(1)</script>'})
+ view = LaunchpadView(object(), request)
+ self.assertEqual(
+ request.form['resize_frame'],
+ '<script>alert(1)</script>'
+ )
+
+ def test_request_form_preserves_safe_values(self):
+ """Test that safe form values are not modified."""
+ request = LaunchpadTestRequest(
+ form={'resize_frame': 'normal123-value.text'})
+ view = LaunchpadView(object(), request)
+ self.assertEqual(
+ request.form['resize_frame'],
+ 'normal123-value.text'
+ )
+
+ def test_request_form_sanitizes_multiple_values(self):
+ """Test that multiple form values containing HTML are escaped."""
+ request = LaunchpadTestRequest(form={
+ 'field1': '<p>test</p>',
+ 'field2': '<script>alert(2)</script>'
+ })
+ view = LaunchpadView(object(), request)
+ self.assertEqual(
+ request.form['field1'],
+ '<p>test</p>'
+ )
+ self.assertEqual(
+ request.form['field2'],
+ '<script>alert(2)</script>'
+ )
+
+ def test_request_form_handles_non_string_values(self):
+ """Test that non-string form values are not modified."""
+ request = LaunchpadTestRequest(form={
+ 'number': 123,
+ 'boolean': True
+ })
+ view = LaunchpadView(object(), request)
+ self.assertEqual(request.form['number'], 123)
+ self.assertEqual(request.form['boolean'], True)
+
+ def test_request_form_handles_empty_values(self):
+ """Test that empty or None form values are handled properly."""
+ request = LaunchpadTestRequest(form={
+ 'empty': '',
+ 'none': None
+ })
+ view = LaunchpadView(object(), request)
+ self.assertEqual(request.form['empty'], '')
+ self.assertIsNone(request.form['none'])
+
+
class TestRedirectionView(TestCase):
layer = DatabaseFunctionalLayer
Follow ups