Skip to content

Commit 05a943e

Browse files
authored
Merge pull request #2235 from plone/plone-hotfix20171128-isURLInPortal-5.1
Improved isURLInPortal according to PloneHotfix20171128. [5.1]
2 parents 482a398 + 1c2818a commit 05a943e

File tree

3 files changed

+81
-7
lines changed

3 files changed

+81
-7
lines changed

CHANGES.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ New features:
3535
Bug fixes:
3636

3737
- Show example for expression in actions control panel.
38+
- Improved isURLInPortal according to PloneHotfix20171128.
39+
Accept only http/https, and doubly check escaped urls. [maurits]
40+
41+
- Fixed Products.CMFPlacefulWorkflow being marked as not installed after upgrade from 4.3.
42+
This is true for any package in the Products namespace that was installed.
43+
Fixes `issue 2103 <https://github.com/plone/Products.CMFPlone/issues/2103>`_.
3844
[maurits]
3945

4046
- Fixed add-on listed as uninstalled when the default profile is not the first alphabetically.

Products/CMFPlone/URLTool.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22
from AccessControl import ClassSecurityInfo
33
from App.class_init import InitializeClass
4+
from HTMLParser import HTMLParser
45
from plone.registry.interfaces import IRegistry
56
from posixpath import normpath
67
from Products.CMFCore.URLTool import URLTool as BaseTool
@@ -11,6 +12,25 @@
1112
import re
1213

1314

15+
hp = HTMLParser()
16+
# These schemas are allowed in full urls to consider them in the portal:
17+
# A mailto schema is an obvious sign of a url that is not in the portal.
18+
# This is a whitelist.
19+
ALLOWED_SCHEMAS = [
20+
'https',
21+
'http',
22+
]
23+
# These bad parts are not allowed in urls that are in the portal:
24+
# This is a blacklist.
25+
BAD_URL_PARTS = [
26+
'\\\\',
27+
'<script',
28+
'%3cscript',
29+
'javascript:',
30+
'javascript%3a',
31+
]
32+
33+
1434
class URLTool(PloneBaseTool, BaseTool):
1535

1636
meta_type = 'Plone URL Tool'
@@ -34,16 +54,24 @@ def isURLInPortal(self, url, context=None):
3454
# sanitize url
3555
url = re.sub('^[\x00-\x20]+', '', url).strip()
3656
cmp_url = url.lower()
37-
if ('\\\\' in cmp_url or
38-
'<script' in cmp_url or
39-
'%3cscript' in cmp_url or
40-
'javascript:' in cmp_url or
41-
'javascript%3a' in cmp_url):
42-
return False
57+
for bad in BAD_URL_PARTS:
58+
if bad in cmp_url:
59+
return False
4360

4461
p_url = self()
4562

46-
_, u_host, u_path, _, _, _ = urlparse(url)
63+
schema, u_host, u_path, _, _, _ = urlparse(url)
64+
if schema and schema not in ALLOWED_SCHEMAS:
65+
# Redirecting to 'data:' may be harmful,
66+
# and redirecting to 'mailto:' or 'ftp:' is silly.
67+
return False
68+
69+
# Someone may be doing tricks with escaped html code.
70+
unescaped_url = hp.unescape(url)
71+
if unescaped_url != url:
72+
if not self.isURLInPortal(unescaped_url):
73+
return False
74+
4775
if not u_host and not u_path.startswith('/'):
4876
if context is None:
4977
return True # old behavior

Products/CMFPlone/tests/testURLTool.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,43 @@ def test_double_back_slash(self):
136136
url_tool = self._makeOne()
137137
iURLiP = url_tool.isURLInPortal
138138
self.assertFalse(iURLiP('\\\\www.example.com'))
139+
140+
def test_regression_absolute_url_in_portal(self):
141+
url_tool = self._makeOne()
142+
iURLiP = url_tool.isURLInPortal
143+
self.assertTrue(iURLiP(url_tool()))
144+
self.assertTrue(iURLiP(url_tool() + '/shrubbery?knights=ni#ekki-ekki'))
145+
146+
def test_mailto_simple_not_in_portal(self):
147+
url_tool = self._makeOne()
148+
iURLiP = url_tool.isURLInPortal
149+
self.assertFalse(iURLiP(
150+
151+
)
152+
153+
def test_mailto_complex_not_in_portal(self):
154+
url_tool = self._makeOne()
155+
iURLiP = url_tool.isURLInPortal
156+
self.assertFalse(iURLiP(
157+
'mailto&#58;192&#46;168&#46;163&#46;154&#58;8080&#47;Plone&apos;'
158+
'&quot;&gt;&lt;html&gt;&lt;svg&#32;onload&#61;alert&#40;document'
159+
'&#46;domain&#41;&gt;&lt;&#47;html&gt;')
160+
)
161+
162+
def test_data_not_in_portal(self):
163+
url_tool = self._makeOne()
164+
iURLiP = url_tool.isURLInPortal
165+
self.assertFalse(iURLiP(
166+
'data:text/html%3bbase64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K')
167+
)
168+
169+
def test_double_slash(self):
170+
# I wondered if this might be a problem after reading
171+
# https://bugs.python.org/issue23505
172+
# Apparently not, but let's test it.
173+
url_tool = self._makeOne()
174+
iURLiP = url_tool.isURLInPortal
175+
self.assertFalse(iURLiP(
176+
'//www.google.com'))
177+
self.assertFalse(iURLiP(
178+
'////www.google.com'))

0 commit comments

Comments
 (0)