Changeset - 638ac4e65365
[Not reviewed]
default
0 2 0
Thomas De Schampheleire - 8 years ago 2018-02-24 21:18:24
thomas.de_schampheleire@nokia.com
issues: gracefully handle invalid issue patterns

issue_pat is provided by the admin and can be invalid due to bad syntax.
In this case, a page load by a user could cause 500 Internal Server Error.

Instead, add a try..except clause around the compilation of issue_pat, and
skip invalid patterns.
2 files changed with 11 insertions and 3 deletions:
0 comments (0 inline, 0 general)
kallithea/lib/helpers.py
Show inline comments
 
@@ -1120,56 +1120,60 @@ def linkify_others(t, l):
 
_urlify_issues_f = None
 

	
 

	
 
def urlify_issues(newtext, repo_name):
 
    """Urlify issue references according to .ini configuration"""
 
    global _urlify_issues_f
 
    if _urlify_issues_f is None:
 
        from kallithea import CONFIG
 
        from kallithea.model.db import URL_SEP
 
        assert CONFIG['sqlalchemy.url'] # make sure config has been loaded
 

	
 
        # Build chain of urlify functions, starting with not doing any transformation
 
        tmp_urlify_issues_f = lambda s: s
 

	
 
        issue_pat_re = re.compile(r'issue_pat(.*)')
 
        for k in CONFIG.keys():
 
            # Find all issue_pat* settings that also have corresponding server_link and prefix configuration
 
            m = issue_pat_re.match(k)
 
            if m is None:
 
                continue
 
            suffix = m.group(1)
 
            issue_pat = CONFIG.get(k)
 
            issue_server_link = CONFIG.get('issue_server_link%s' % suffix)
 
            issue_prefix = CONFIG.get('issue_prefix%s' % suffix)
 
            if issue_pat and issue_server_link and issue_prefix is not None: # issue_prefix can be empty but should be present
 
                log.debug('issue pattern %r: %r -> %r %r', suffix, issue_pat, issue_server_link, issue_prefix)
 
            else:
 
            if not issue_pat or not issue_server_link or issue_prefix is None: # issue_prefix can be empty but should be present
 
                log.error('skipping incomplete issue pattern %r: %r -> %r %r', suffix, issue_pat, issue_server_link, issue_prefix)
 
                continue
 

	
 
            # Wrap tmp_urlify_issues_f with substitution of this pattern, while making sure all loop variables (and compiled regexpes) are bound
 
            try:
 
            issue_re = re.compile(issue_pat)
 
            except re.error as e:
 
                log.error('skipping invalid issue pattern %r: %r -> %r %r. Error: %s', suffix, issue_pat, issue_server_link, issue_prefix, str(e))
 
                continue
 

	
 
            log.debug('issue pattern %r: %r -> %r %r', suffix, issue_pat, issue_server_link, issue_prefix)
 

	
 
            def issues_replace(match_obj,
 
                               issue_server_link=issue_server_link, issue_prefix=issue_prefix):
 
                leadingspace = ' ' if match_obj.group().startswith(' ') else ''
 
                issue_id = ''.join(match_obj.groups())
 
                issue_url = issue_server_link.replace('{id}', issue_id)
 
                issue_url = issue_url.replace('{repo}', repo_name)
 
                issue_url = issue_url.replace('{repo_name}', repo_name.split(URL_SEP)[-1])
 
                return (
 
                    '%(leadingspace)s<a class="issue-tracker-link" href="%(url)s">'
 
                    '%(issue-prefix)s%(id-repr)s'
 
                    '</a>'
 
                    ) % {
 
                     'leadingspace': leadingspace,
 
                     'url': issue_url,
 
                     'id-repr': issue_id,
 
                     'issue-prefix': issue_prefix,
 
                     'serv': issue_server_link,
 
                    }
 
            tmp_urlify_issues_f = (lambda s,
 
                                          issue_re=issue_re, issues_replace=issues_replace, chain_f=tmp_urlify_issues_f:
 
                                   issue_re.sub(issues_replace, chain_f(s)))
 

	
 
        # Set tmp function globally - atomically
kallithea/tests/other/test_libs.py
Show inline comments
 
@@ -439,48 +439,52 @@ class TestLibs(TestController):
 
            'interesting issue #123',
 
            """interesting issue <a class="issue-tracker-link" href="http://foo/repo_name/issue/123">PR123</a>"""),
 
        (r'BUG\d{5}', 'https://bar/{repo}/{id}', 'BUG',
 
            'silly me, I did not parenthesize the {id}, BUG12345.',
 
            """silly me, I did not parenthesize the {id}, <a class="issue-tracker-link" href="https://bar/repo_name/">BUG</a>."""),
 
        (r'BUG(\d{5})', 'https://bar/{repo}/', 'BUG',
 
            'silly me, the URL does not contain {id}, BUG12345.',
 
            """silly me, the URL does not contain {id}, <a class="issue-tracker-link" href="https://bar/repo_name/">BUG12345</a>."""),
 
        (r'(PR-\d+)', 'http://foo/{repo}/issue/{id}', '',
 
            'interesting issue #123, err PR-56',
 
            """interesting issue #123, err <a class="issue-tracker-link" href="http://foo/repo_name/issue/PR-56">PR-56</a>"""),
 
        (r'#(\d+)', 'http://foo/{repo}/issue/{id}', '#',
 
            "some 'standard' text with apostrophes",
 
            """some &#39;standard&#39; text with apostrophes"""),
 
        (r'#(\d+)', 'http://foo/{repo}/issue/{id}', '#',
 
            "some 'standard' issue #123",
 
            """some &#39;standard&#39; issue <a class="issue-tracker-link" href="http://foo/repo_name/issue/123">#123</a>"""),
 
        (r'#(\d+)', 'http://foo/{repo}/issue/{id}', '#',
 
            'an issue   #123       with extra whitespace',
 
            """an issue   <a class="issue-tracker-link" href="http://foo/repo_name/issue/123">#123</a>       with extra whitespace"""),
 
        # Note: whitespace is squashed
 
        (r'(?:\s*#)(\d+)', 'http://foo/{repo}/issue/{id}', '#',
 
            'an issue   #123       with extra whitespace',
 
            """an issue <a class="issue-tracker-link" href="http://foo/repo_name/issue/123">#123</a>       with extra whitespace"""),
 
        # invalid issue pattern
 
        (r'(PR\d+', 'http://foo/{repo}/issue/{id}', '',
 
            'PR135',
 
            """PR135"""),
 
    ])
 
    def test_urlify_issues(self, issue_pat, issue_server, issue_prefix, sample, expected):
 
        from kallithea.lib.helpers import urlify_text
 
        config_stub = {
 
            'sqlalchemy.url': 'foo',
 
            'issue_pat': issue_pat,
 
            'issue_server_link': issue_server,
 
            'issue_prefix': issue_prefix,
 
        }
 
        # force recreation of lazy function
 
        with mock.patch('kallithea.lib.helpers._urlify_issues_f', None):
 
            with mock.patch('kallithea.CONFIG', config_stub):
 
                assert urlify_text(sample, 'repo_name') == expected
 

	
 
    @parametrize('sample,expected', [
 
        ('abc X5', 'abc <a class="issue-tracker-link" href="http://main/repo_name/main/5/">#5</a>'),
 
        ('abc pullrequest #6 xyz', 'abc <a class="issue-tracker-link" href="http://pr/repo_name/pr/6">PR#6</a> xyz'),
 
        ('pull request7 #', '<a class="issue-tracker-link" href="http://pr/repo_name/pr/7">PR#7</a> #'),
 
        ('look PR9 and pr #11', 'look <a class="issue-tracker-link" href="http://pr/repo_name/pr/9">PR#9</a> and <a class="issue-tracker-link" href="http://pr/repo_name/pr/11">PR#11</a>'),
 
        ('pullrequest#10 solves issue 9', '<a class="issue-tracker-link" href="http://pr/repo_name/pr/10">PR#10</a> solves <a class="issue-tracker-link" href="http://bug/repo_name/bug/9">bug#9</a>'),
 
        ('issue FAIL67', 'issue <a class="issue-tracker-link" href="http://fail/repo_name/67">67</a>'),
 
        ('issue FAILMORE89', 'issue FAILMORE89'), # no match because absent prefix
 
    ])
 
    def test_urlify_issues_multiple_issue_patterns(self, sample, expected):
0 comments (0 inline, 0 general)