diff --git a/qutebrowser/browser/curcommand.py b/qutebrowser/browser/curcommand.py index e086db275..9b4caa884 100644 --- a/qutebrowser/browser/curcommand.py +++ b/qutebrowser/browser/curcommand.py @@ -28,12 +28,13 @@ from PyQt5.QtCore import pyqtSlot, Qt, QObject, QProcess from PyQt5.QtGui import QClipboard from PyQt5.QtPrintSupport import QPrintDialog, QPrintPreviewDialog -import qutebrowser.utils.url as urlutils -import qutebrowser.utils.message as message import qutebrowser.commands.utils as cmdutils -import qutebrowser.utils.webelem as webelem import qutebrowser.config.config as config import qutebrowser.browser.hints as hints +import qutebrowser.utils.url as urlutils +import qutebrowser.utils.message as message +import qutebrowser.utils.webelem as webelem +import qutebrowser.utils.misc as utils from qutebrowser.utils.misc import shell_escape from qutebrowser.commands.exceptions import CommandError @@ -75,6 +76,7 @@ class CurCommandDispatcher(QObject): perc = int(count) else: perc = float(perc) + perc = utils.check_overflow(perc, 'int', fatal=False) frame = self._tabs.currentWidget().page_.currentFrame() m = frame.scrollBarMaximum(orientation) if m == 0: @@ -294,6 +296,8 @@ class CurCommandDispatcher(QObject): """ dx = int(int(count) * float(dx)) dy = int(int(count) * float(dy)) + cmdutils.check_overflow(dx, 'int') + cmdutils.check_overflow(dy, 'int') self._tabs.currentWidget().page_.currentFrame().scroll(dx, dy) @cmdutils.register(instance='mainwindow.tabs.cur', name='scroll_perc_x', @@ -333,8 +337,11 @@ class CurCommandDispatcher(QObject): """ frame = self._tabs.currentWidget().page_.currentFrame() size = frame.geometry() - frame.scroll(int(count) * float(mx) * size.width(), - int(count) * float(my) * size.height()) + dx = int(count) * float(mx) * size.width() + dy = int(count) * float(my) * size.height() + cmdutils.check_overflow(dx, 'int') + cmdutils.check_overflow(dy, 'int') + frame.scroll(dx, dy) @cmdutils.register(instance='mainwindow.tabs.cur') def yank(self, sel=False): diff --git a/qutebrowser/commands/utils.py b/qutebrowser/commands/utils.py index b3d13fde2..d7e2708c7 100644 --- a/qutebrowser/commands/utils.py +++ b/qutebrowser/commands/utils.py @@ -24,11 +24,32 @@ Module attributes: import inspect from collections import Iterable +import qutebrowser.utils.misc as utils from qutebrowser.commands._command import Command +from qutebrowser.commands.exceptions import CommandError cmd_dict = {} +def check_overflow(arg, ctype): + """Check if the given argument is in bounds for the given type. + + Args: + arg: The argument to check + ctype: The C/Qt type to check as a string. + + Raise: + CommandError if the argument is out of bounds. + ValueError if the given ctype is unknown. + """ + # FIXME we somehow should have nicer exceptions... + try: + utils.check_overflow(arg, ctype) + except OverflowError: + raise CommandError("Numeric argument is too large for internal {} " + "representation.".format(ctype)) + + def arg_or_count(arg, count, default=None, countzero=None): """Get a value based on an argument and count given to a command. diff --git a/qutebrowser/config/_conftypes.py b/qutebrowser/config/_conftypes.py index 82521b9d2..28e33b097 100644 --- a/qutebrowser/config/_conftypes.py +++ b/qutebrowser/config/_conftypes.py @@ -568,6 +568,8 @@ class WebKitBytes(BaseType): if self.maxsize is not None and val > self.maxsize: raise ValidationError(value, "must be {} " "maximum!".format(self.maxsize)) + if val < 0: + raise ValidationError(value, "must be 0 minimum!") def transform(self, value): if value == '': diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 32356093c..fffda7e06 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -25,16 +25,12 @@ DATA: The config defaults, an OrderedDict of sections. """ import re -import struct from collections import OrderedDict from qutebrowser.config._value import SettingValue import qutebrowser.config._conftypes as types import qutebrowser.config._sections as sect - - -INT_MAX = 2 ** (8 * struct.calcsize('@i')) // 2 - 1 -INT64_MAX = 2 ** 64 // 2 - 1 +from qutebrowser.utils.misc import MAXVALS FIRST_COMMENT = r""" @@ -253,7 +249,7 @@ DATA = OrderedDict([ ('input', sect.KeyValue( ('timeout', - SettingValue(types.Int(minval=0), '500'), + SettingValue(types.Int(minval=0, maxval=MAXVALS['int']), '500'), "Timeout for ambiguous keybindings."), ('insert-mode-on-plugins', @@ -301,11 +297,11 @@ DATA = OrderedDict([ "Whether to wrap when changing tabs."), ('min-tab-width', - SettingValue(types.Int(), '100'), + SettingValue(types.Int(minval=1), '100'), "The minimum width of a tab."), ('max-tab-width', - SettingValue(types.Int(), '200'), + SettingValue(types.Int(minval=1), '200'), "The maximum width of a tab."), ('show-favicons', @@ -477,37 +473,43 @@ DATA = OrderedDict([ "Font family for fantasy fonts."), ('font-size-minimum', - SettingValue(types.Int(none=True), ''), + SettingValue(types.Int(none=True, minval=1, maxval=MAXVALS['int']), + ''), "The hard minimum font size."), ('font-size-minimum-logical', - SettingValue(types.Int(none=True), ''), + SettingValue(types.Int(none=True, minval=1, maxval=MAXVALS['int']), + ''), "The minimum logical font size that is applied when zooming out."), ('font-size-default', - SettingValue(types.Int(none=True), ''), + SettingValue(types.Int(none=True, minval=1, maxval=MAXVALS['int']), + ''), "The default font size for regular text."), ('font-size-default-fixed', - SettingValue(types.Int(none=True), ''), + SettingValue(types.Int(none=True, minval=1, maxval=MAXVALS['int']), + ''), "The default font size for fixed-pitch text."), ('maximum-pages-in-cache', - SettingValue(types.Int(none=True), ''), + SettingValue(types.Int(none=True, minval=0, maxval=MAXVALS['int']), + ''), "Sets the maximum number of pages to hold in the memory page cache."), ('object-cache-capacities', - SettingValue(types.WebKitBytesList(length=3, maxsize=INT_MAX), ''), + SettingValue(types.WebKitBytesList(length=3, maxsize=MAXVALS['int']), + ''), "Specifies the capacities for the memory cache for dead objects " "such as stylesheets or scripts. Three values are expected: " "cacheMinDeadCapacity, cacheMaxDead, totalCapacity"), ('offline-storage-default-quota', - SettingValue(types.WebKitBytes(maxsize=INT64_MAX), ''), + SettingValue(types.WebKitBytes(maxsize=MAXVALS['int64']), ''), "Default quota for new offline storage databases."), ('offline-web-application-cache-quota', - SettingValue(types.WebKitBytes(maxsize=INT64_MAX), ''), + SettingValue(types.WebKitBytes(maxsize=MAXVALS['int64']), ''), "Quota for the offline web application cache."), )), diff --git a/qutebrowser/test/utils/test_misc.py b/qutebrowser/test/utils/test_misc.py index 7c8790fac..db6afd33c 100644 --- a/qutebrowser/test/utils/test_misc.py +++ b/qutebrowser/test/utils/test_misc.py @@ -34,6 +34,48 @@ import qutebrowser.utils.misc as utils from qutebrowser.test.helpers import environ_set_temp +class CheckOverflowTests(TestCase): + + """Test check_overflow.""" + + INT32_MIN = -(2 ** 31) + INT32_MAX = 2 ** 31 - 1 + INT64_MIN = -(2 ** 63) + INT64_MAX = 2 ** 63 - 1 + + GOOD_VALUES = { + 'int': [-1, 0, 1, 23.42, INT32_MIN, INT32_MAX], + 'int64': [-1, 0, 1, 23.42, INT64_MIN, INT64_MAX], + } + + BAD_VALUES = { + 'int': [(INT32_MIN - 1, INT32_MIN), + (INT32_MAX + 1, INT32_MAX), + (float(INT32_MAX + 1), INT32_MAX)], + 'int64': [(INT64_MIN - 1, INT64_MIN), + (INT64_MAX + 1, INT64_MAX), + (float(INT64_MAX + 1), INT64_MAX)], + } + + def test_good_values(self): + for ctype, vals in self.GOOD_VALUES.items(): + for val in vals: + utils.check_overflow(val, ctype) + + def test_bad_values_fatal(self): + for ctype, vals in self.BAD_VALUES.items(): + for (val, _) in vals: + with self.assertRaises(OverflowError, msg=ctype): + utils.check_overflow(val, ctype) + + def test_bad_values_nonfatal(self): + for ctype, vals in self.BAD_VALUES.items(): + for (val, replacement) in vals: + newval = utils.check_overflow(val, ctype, fatal=False) + self.assertEqual(newval, replacement, + "{}: {}".format(ctype, val)) + + class ReadFileTests(TestCase): """Test read_file.""" diff --git a/qutebrowser/utils/misc.py b/qutebrowser/utils/misc.py index b9416f491..badf12681 100644 --- a/qutebrowser/utils/misc.py +++ b/qutebrowser/utils/misc.py @@ -15,7 +15,12 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""Other utilities which don't fit anywhere else.""" +"""Other utilities which don't fit anywhere else. + +Module attributes: + MAXVAL: A dictionary of C/Qt types (as string) mapped to their maximum + value. +""" import os import re @@ -31,6 +36,49 @@ from PyQt5.QtCore import QCoreApplication, QStandardPaths import qutebrowser +MAXVALS = { + 'int': 2 ** 31 - 1, + 'int64': 2 ** 63 - 1, +} + +MINVALS = { + 'int': -(2 ** 31), + 'int64': -(2 ** 63), +} + + +def check_overflow(arg, ctype, fatal=True): + """Check if the given argument is in bounds for the given type. + + Args: + arg: The argument to check + ctype: The C/Qt type to check as a string. + fatal: Wether to raise exceptions (True) or truncate values (False) + + Return + The truncated argument if fatal=False + The original argument if it's in bounds. + + Raise: + OverflowError if the argument is out of bounds and fatal=True. + """ + # FIXME we somehow should have nicer exceptions... + maxval = MAXVALS[ctype] + minval = MINVALS[ctype] + if arg > maxval: + if fatal: + raise OverflowError(arg) + else: + return maxval + elif arg < minval: + if fatal: + raise OverflowError(arg) + else: + return minval + else: + return arg + + def read_file(filename): """Get the contents of a file contained with qutebrowser. diff --git a/qutebrowser/widgets/_tabbedbrowser.py b/qutebrowser/widgets/_tabbedbrowser.py index 8b5d218f3..f52c8210c 100644 --- a/qutebrowser/widgets/_tabbedbrowser.py +++ b/qutebrowser/widgets/_tabbedbrowser.py @@ -246,6 +246,7 @@ class TabbedBrowser(TabWidget): if count is None: return self.currentWidget() elif 1 <= count <= self.count(): + cmdutils.check_overflow(count + 1, 'int') return self.widget(count - 1) else: return None @@ -418,6 +419,7 @@ class TabbedBrowser(TabWidget): countzero=self.count()) except ValueError as e: raise CommandError(e) + cmdutils.check_overflow(idx + 1, 'int') if 1 <= idx <= self.count(): self.setCurrentIndex(idx - 1) else: @@ -448,6 +450,8 @@ class TabbedBrowser(TabWidget): cur_idx = self.currentIndex() icon = self.tabIcon(cur_idx) label = self.tabText(cur_idx) + cmdutils.check_overflow(cur_idx, 'int') + cmdutils.check_overflow(next_idx, 'int') self.removeTab(cur_idx) self.insertTab(new_idx, tab, icon, label) self.setCurrentIndex(new_idx) diff --git a/qutebrowser/widgets/mainwindow.py b/qutebrowser/widgets/mainwindow.py index 892d7df0a..3e0c1c5d6 100644 --- a/qutebrowser/widgets/mainwindow.py +++ b/qutebrowser/widgets/mainwindow.py @@ -24,11 +24,12 @@ from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QCoreApplication from PyQt5.QtWidgets import QWidget, QVBoxLayout from PyQt5.QtWebKitWidgets import QWebInspector +import qutebrowser.commands.utils as cmdutils +import qutebrowser.config.config as config +import qutebrowser.utils.misc as utils from qutebrowser.widgets.statusbar.bar import StatusBar from qutebrowser.widgets._tabbedbrowser import TabbedBrowser from qutebrowser.widgets._completion import CompletionView -import qutebrowser.commands.utils as cmdutils -import qutebrowser.config.config as config class MainWindow(QWidget): @@ -102,7 +103,9 @@ class MainWindow(QWidget): height = int(confheight) # hpoint now would be the bottom-left edge of the widget if it was on # the top of the main window. - topleft = QPoint(0, self.height() - self.status.height() - height) + topleft_y = self.height() - self.status.height() - height + topleft_y = utils.check_overflow(topleft_y, 'int', fatal=False) + topleft = QPoint(0, topleft_y) bottomright = self.status.geometry().topRight() if self.inspector.isVisible(): topleft -= QPoint(0, self.inspector.height())