changeset 7404:22c8f23cc75b

Merge stable
author Mads Kiilerich <mads@kiilerich.com>
date Mon, 05 Nov 2018 00:31:07 +0100
parents 475d54df23f5 (current diff) d85ce8c88f0d (diff)
children e4b9a1d1fea1
files kallithea/__init__.py kallithea/lib/base.py kallithea/lib/markup_renderer.py kallithea/lib/middleware/simplehg.py setup.py
diffstat 5 files changed, 101 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Mon Oct 29 01:20:21 2018 +0100
+++ b/.hgtags	Mon Nov 05 00:31:07 2018 +0100
@@ -67,3 +67,4 @@
 64ea7ea0923618a0c117acebb816a6f0d162bfdb 0.3.3
 cf635c823ea059cc3a1581b82d8672e46b682384 0.3.4
 4cca4cc6a0a97f4c4763317184cd41aca4297630 0.3.5
+082c9b8f0f17bd34740eb90c69bdc4c80d4b5b31 0.3.6
--- a/kallithea/lib/base.py	Mon Oct 29 01:20:21 2018 +0100
+++ b/kallithea/lib/base.py	Mon Nov 05 00:31:07 2018 +0100
@@ -328,7 +328,7 @@
         Checks permissions using action (push/pull) user and repository
         name
 
-        :param action: push or pull action
+        :param action: 'push' or 'pull' action
         :param user: `User` instance
         :param repo_name: repository name
         """
--- a/kallithea/lib/markup_renderer.py	Mon Oct 29 01:20:21 2018 +0100
+++ b/kallithea/lib/markup_renderer.py	Mon Nov 05 00:31:07 2018 +0100
@@ -30,6 +30,9 @@
 import logging
 import traceback
 
+import markdown as markdown_mod
+import bleach
+
 from kallithea.lib.utils2 import safe_unicode, MENTIONS_REGEX
 
 log = logging.getLogger(__name__)
@@ -138,17 +141,43 @@
 
     @classmethod
     def markdown(cls, source, safe=True, flavored=False):
+        """
+        Convert Markdown (possibly GitHub Flavored) to XSS safe HTML, possibly
+        with "safe" fall-back to plaintext.
+
+        >>> MarkupRenderer.markdown('''<img id="a" style="margin-top:-1000px;color:red" src="http://example.com/test.jpg">''')
+        u'<p><img id="a" src="http://example.com/test.jpg" style="color: red;"></p>'
+        >>> MarkupRenderer.markdown('''<img class="c d" src="file://localhost/test.jpg">''')
+        u'<p><img class="c d"></p>'
+        >>> MarkupRenderer.markdown('''<a href="foo">foo</a>''')
+        u'<p><a href="foo">foo</a></p>'
+        >>> MarkupRenderer.markdown('''<script>alert(1)</script>''')
+        u'&lt;script&gt;alert(1)&lt;/script&gt;'
+        >>> MarkupRenderer.markdown('''<div onclick="alert(2)">yo</div>''')
+        u'<div>yo</div>'
+        >>> MarkupRenderer.markdown('''<a href="javascript:alert(3)">yo</a>''')
+        u'<p><a>yo</a></p>'
+        """
         source = safe_unicode(source)
         try:
-            import markdown as __markdown
             if flavored:
                 source = cls._flavored_markdown(source)
-            return __markdown.markdown(source,
+            markdown_html = markdown_mod.markdown(source,
                                        extensions=['codehilite', 'extra'],
                                        extension_configs={'codehilite': {'css_class': 'code-highlight'}})
-        except ImportError:
-            log.warning('Install markdown to use this function')
-            return cls.plain(source)
+            # Allow most HTML, while preventing XSS issues:
+            # no <script> tags, no onclick attributes, no javascript
+            # "protocol", and also limit styling to prevent defacing.
+            return bleach.clean(markdown_html,
+                tags=['a', 'abbr', 'b', 'blockquote', 'br', 'code', 'dd',
+                      'div', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5',
+                      'h6', 'hr', 'i', 'img', 'li', 'ol', 'p', 'pre', 'span',
+                      'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'th',
+                      'thead', 'tr', 'ul'],
+                attributes=['class', 'id', 'style', 'label', 'title', 'alt', 'href', 'src'],
+                styles=['color'],
+                protocols=['http', 'https', 'mailto'],
+                )
         except Exception:
             log.error(traceback.format_exc())
             if safe:
--- a/kallithea/lib/middleware/simplehg.py	Mon Oct 29 01:20:21 2018 +0100
+++ b/kallithea/lib/middleware/simplehg.py	Mon Nov 05 00:31:07 2018 +0100
@@ -31,6 +31,7 @@
 import os
 import logging
 import traceback
+import urllib
 
 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
     HTTPNotAcceptable, HTTPBadRequest
@@ -63,6 +64,24 @@
     return ishg_path
 
 
+def get_header_hgarg(environ):
+    """Decode the special Mercurial encoding of big requests over multiple headers.
+    >>> get_header_hgarg({})
+    ''
+    >>> get_header_hgarg({'HTTP_X_HGARG_0': ' ', 'HTTP_X_HGARG_1': 'a','HTTP_X_HGARG_2': '','HTTP_X_HGARG_3': 'b+c %20'})
+    'ab+c %20'
+    """
+    chunks = []
+    i = 1
+    while True:
+        v = environ.get('HTTP_X_HGARG_%d' % i)
+        if v is None:
+            break
+        chunks.append(v)
+        i += 1
+    return ''.join(chunks)
+
+
 class SimpleHg(BaseVCSController):
 
     def _handle_request(self, environ, start_response):
@@ -205,16 +224,55 @@
 
     def __get_action(self, environ):
         """
-        Maps Mercurial request commands into a pull or push command.
+        Maps Mercurial request commands into 'pull' or 'push'.
 
         Raises HTTPBadRequest if the request environment doesn't look like a hg client.
         """
-        mapping = {'unbundle': 'push',
-                   'pushkey': 'push'}
+        mapping = {
+            # 'batch' is not in this list - it is handled explicitly
+            'between': 'pull',
+            'branches': 'pull',
+            'branchmap': 'pull',
+            'capabilities': 'pull',
+            'changegroup': 'pull',
+            'changegroupsubset': 'pull',
+            'changesetdata': 'pull',
+            'clonebundles': 'pull',
+            'debugwireargs': 'pull',
+            'filedata': 'pull',
+            'getbundle': 'pull',
+            'getlfile': 'pull',
+            'heads': 'pull',
+            'hello': 'pull',
+            'known': 'pull',
+            'lheads': 'pull',
+            'listkeys': 'pull',
+            'lookup': 'pull',
+            'manifestdata': 'pull',
+            'narrow_widen': 'pull',
+            'protocaps': 'pull',
+            'statlfile': 'pull',
+            'stream_out': 'pull',
+            'pushkey': 'push',
+            'putlfile': 'push',
+            'unbundle': 'push',
+            }
         for qry in environ['QUERY_STRING'].split('&'):
-            if qry.startswith('cmd'):
-                cmd = qry.split('=')[-1]
-                return mapping.get(cmd, 'pull')
+            parts = qry.split('=', 1)
+            if len(parts) == 2 and parts[0] == 'cmd':
+                cmd = parts[1]
+                if cmd == 'batch':
+                    hgarg = get_header_hgarg(environ)
+                    if not hgarg.startswith('cmds='):
+                        return 'push' # paranoid and safe
+                    for cmd_arg in hgarg[5:].split(';'):
+                        cmd, _args = urllib.unquote_plus(cmd_arg).split(' ', 1)
+                        op = mapping.get(cmd, 'push')
+                        if op != 'pull':
+                            assert op == 'push'
+                            return 'push'
+                    return 'pull'
+                return mapping.get(cmd, 'push')
 
         # Note: the client doesn't get the helpful error message
         raise HTTPBadRequest('Unable to detect pull/push action! Are you using non standard command or client?')
--- a/setup.py	Mon Oct 29 01:20:21 2018 +0100
+++ b/setup.py	Mon Nov 05 00:31:07 2018 +0100
@@ -60,6 +60,7 @@
     "mercurial >= 4.1.1, < 4.9",
     "decorator >= 3.3.2, < 4.4",
     "Paste >= 2.0.3, < 3",
+    "bleach >= 3.0, < 3.1",
 ]
 
 if sys.version_info < (2, 7):