changeset 7124:55d2b08d9c44 stable

vcs: sanitize diff context values (Issue #306) - Updated Git repository implementation to ensure context falls within 0 to 2**31-1 range (inclusive) when fetching a diff. - Added tests for Git repositories for checking passed-in negative and overflowing contexts (for the --unified option). - Updated Mercurial repository implementation to ensure context is not negative when fetching a diff. - Added tests for Mercurial repositories for checking passed-in negative context (for the --unified option).
author Branko Majic <branko@majic.rs>
date Fri, 09 Feb 2018 18:12:19 +0100
parents cefb13bad9b5
children 2a96678c8cd9 b4a5632733d9
files kallithea/lib/vcs/backends/git/repository.py kallithea/lib/vcs/backends/hg/repository.py kallithea/tests/vcs/test_git.py kallithea/tests/vcs/test_hg.py
diffstat 4 files changed, 91 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/lib/vcs/backends/git/repository.py	Tue Jan 30 14:35:03 2018 +0100
+++ b/kallithea/lib/vcs/backends/git/repository.py	Fri Feb 09 18:12:19 2018 +0100
@@ -587,8 +587,30 @@
         :param ignore_whitespace: If set to ``True``, would not show whitespace
           changes. Defaults to ``False``.
         :param context: How many lines before/after changed lines should be
-          shown. Defaults to ``3``.
+          shown. Defaults to ``3``. Due to limitations in Git, if
+          value passed-in is greater than ``2**31-1``
+          (``2147483647``), it will be set to ``2147483647``
+          instead. If negative value is passed-in, it will be set to
+          ``0`` instead.
         """
+
+        # Git internally uses a signed long int for storing context
+        # size (number of lines to show before and after the
+        # differences). This can result in integer overflow, so we
+        # ensure the requested context is smaller by one than the
+        # number that would cause the overflow. It is highly unlikely
+        # that a single file will contain that many lines, so this
+        # kind of change should not cause any realistic consequences.
+        overflowed_long_int = 2**31
+
+        if context >= overflowed_long_int:
+            context = overflowed_long_int-1
+
+        # Negative context values make no sense, and will result in
+        # errors. Ensure this does not happen.
+        if context < 0:
+            context = 0
+
         flags = ['-U%s' % context, '--full-index', '--binary', '-p', '-M', '--abbrev=40']
         if ignore_whitespace:
             flags.append('-w')
--- a/kallithea/lib/vcs/backends/hg/repository.py	Tue Jan 30 14:35:03 2018 +0100
+++ b/kallithea/lib/vcs/backends/hg/repository.py	Fri Feb 09 18:12:19 2018 +0100
@@ -244,8 +244,15 @@
         :param ignore_whitespace: If set to ``True``, would not show whitespace
           changes. Defaults to ``False``.
         :param context: How many lines before/after changed lines should be
-          shown. Defaults to ``3``.
+          shown. Defaults to ``3``. If negative value is passed-in, it will be
+          set to ``0`` instead.
         """
+
+        # Negative context values make no sense, and will result in
+        # errors. Ensure this does not happen.
+        if context < 0:
+            context = 0
+
         if hasattr(rev1, 'raw_id'):
             rev1 = getattr(rev1, 'raw_id')
 
--- a/kallithea/tests/vcs/test_git.py	Tue Jan 30 14:35:03 2018 +0100
+++ b/kallithea/tests/vcs/test_git.py	Fri Feb 09 18:12:19 2018 +0100
@@ -727,6 +727,46 @@
             ['diff', '-U3', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
              self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'])
 
+    def test_get_diff_does_not_sanitize_valid_context(self):
+        almost_overflowed_long_int = 2**31-1
+
+        self.repo.run_git_command = mock.Mock(return_value=['', ''])
+        self.repo.get_diff(0, 1, 'foo', context=almost_overflowed_long_int)
+        self.repo.run_git_command.assert_called_once_with(
+            ['diff', '-U' + str(almost_overflowed_long_int), '--full-index', '--binary', '-p', '-M', '--abbrev=40',
+             self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'])
+
+    def test_get_diff_sanitizes_overflowing_context(self):
+        overflowed_long_int = 2**31
+        sanitized_overflowed_long_int = overflowed_long_int-1
+
+        self.repo.run_git_command = mock.Mock(return_value=['', ''])
+        self.repo.get_diff(0, 1, 'foo', context=overflowed_long_int)
+
+        self.repo.run_git_command.assert_called_once_with(
+            ['diff', '-U' + str(sanitized_overflowed_long_int), '--full-index', '--binary', '-p', '-M', '--abbrev=40',
+             self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'])
+
+    def test_get_diff_does_not_sanitize_zero_context(self):
+        zero_context = 0
+
+        self.repo.run_git_command = mock.Mock(return_value=['', ''])
+        self.repo.get_diff(0, 1, 'foo', context=zero_context)
+
+        self.repo.run_git_command.assert_called_once_with(
+            ['diff', '-U' + str(zero_context), '--full-index', '--binary', '-p', '-M', '--abbrev=40',
+             self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'])
+
+    def test_get_diff_sanitizes_negative_context(self):
+        negative_context = -10
+
+        self.repo.run_git_command = mock.Mock(return_value=['', ''])
+        self.repo.get_diff(0, 1, 'foo', context=negative_context)
+
+        self.repo.run_git_command.assert_called_once_with(
+            ['diff', '-U0', '--full-index', '--binary', '-p', '-M', '--abbrev=40',
+             self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'])
+
 
 class GitRegressionTest(_BackendTestMixin, unittest.TestCase):
     backend_alias = 'git'
--- a/kallithea/tests/vcs/test_hg.py	Tue Jan 30 14:35:03 2018 +0100
+++ b/kallithea/tests/vcs/test_hg.py	Fri Feb 09 18:12:19 2018 +0100
@@ -1,5 +1,8 @@
 
 import os
+
+import mock
+
 from kallithea.lib.vcs.backends.hg import MercurialRepository, MercurialChangeset
 from kallithea.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError
 from kallithea.lib.vcs.nodes import NodeKind, NodeState
@@ -231,6 +234,23 @@
         self.assertEqual(node.kind, NodeKind.FILE)
         self.assertEqual(node.content, README)
 
+    @mock.patch('kallithea.lib.vcs.backends.hg.repository.diffopts')
+    def test_get_diff_does_not_sanitize_zero_context(self, mock_diffopts):
+        zero_context = 0
+
+        self.repo.get_diff(0, 1, 'foo', context=zero_context)
+
+        mock_diffopts.assert_called_once_with(git=True, showfunc=True, ignorews=False, context=zero_context)
+
+    @mock.patch('kallithea.lib.vcs.backends.hg.repository.diffopts')
+    def test_get_diff_sanitizes_negative_context(self, mock_diffopts):
+        negative_context = -10
+        zero_context = 0
+
+        self.repo.get_diff(0, 1, 'foo', context=negative_context)
+
+        mock_diffopts.assert_called_once_with(git=True, showfunc=True, ignorews=False, context=zero_context)
+
 
 class MercurialChangesetTest(unittest.TestCase):