From b10c934e154fbe0c3709af122a27e9315d06f591 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 21 Apr 2014 21:04:19 +0200 Subject: [PATCH] Validate length/bounds for config values --- qutebrowser/config/configdata.py | 166 ++++++++++++++++--------------- qutebrowser/config/conftypes.py | 125 +++++++++++++++++++---- qutebrowser/config/sections.py | 6 +- qutebrowser/config/value.py | 4 +- 4 files changed, 193 insertions(+), 108 deletions(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 84b3f3d9d..bf52d414a 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -107,188 +107,190 @@ SECTION_DESC = { DATA = OrderedDict([ ('general', sect.KeyValue( ('show_completion', - SettingValue(types.Bool, "true"), + SettingValue(types.Bool(), "true"), "Whether to show the autocompletion window or not."), ('completion_height', - SettingValue(types.PercOrInt, "50%"), + SettingValue(types.PercOrInt(minperc=0, maxperc=100, minint=1), + "50%"), "The height of the completion, in px or as percentage of the " "window."), ('ignorecase', - SettingValue(types.Bool, "true"), + SettingValue(types.Bool(), "true"), "Whether to do case-insensitive searching."), ('wrapsearch', - SettingValue(types.Bool, "true"), + SettingValue(types.Bool(), "true"), "Whether to wrap search to the top when arriving at the end."), ('startpage', - SettingValue(types.List, "http://www.duckduckgo.com"), + SettingValue(types.List(), "http://www.duckduckgo.com"), "The default page(s) to open at the start, separated with commas."), ('auto_search', - SettingValue(types.AutoSearch, "naive"), + SettingValue(types.AutoSearch(), "naive"), "Whether to start a search when something else than an URL is " "entered."), ('zoomlevels', - SettingValue(types.PercList, "25%,33%,50%,67%,75%,90%,100%,110%,125%," - "150%,175%,200%,250%,300%,400%,500%"), + SettingValue(types.PercList(minval=0), + "25%,33%,50%,67%,75%,90%,100%,110%,125%,150%,175%,200%," + "250%,300%,400%,500%"), "The available zoom levels, separated by commas."), ('defaultzoom', - SettingValue(types.ZoomPerc, "100%"), + SettingValue(types.ZoomPerc(), "100%"), "The default zoom level."), ('autosave', - SettingValue(types.Bool, "true"), + SettingValue(types.Bool(), "true"), "Whether to save the config automatically on quit."), ('cmd_histlen', - SettingValue(types.Int, "100"), + SettingValue(types.Int(minval=-1), "100"), "How many commands to save in the history. 0: no history / -1: " "unlimited"), ('background_tabs', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Whether to open new tabs (middleclick/ctrl+click) in background"), )), ('tabbar', sect.KeyValue( ('movable', - SettingValue(types.Bool, "true"), + SettingValue(types.Bool(), "true"), "Whether tabs should be movable."), ('closebuttons', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Whether tabs should have close-buttons."), ('scrollbuttons', - SettingValue(types.Bool, "true"), + SettingValue(types.Bool(), "true"), "Whether there should be scroll buttons if there are too many tabs."), ('position', - SettingValue(types.Position, "north"), + SettingValue(types.Position(), "north"), "The position of the tab bar."), ('select_on_remove', - SettingValue(types.SelectOnRemove, "previous"), + SettingValue(types.SelectOnRemove(), "previous"), "Which tab to select when the focused tab is removed."), ('last_close', - SettingValue(types.LastClose, "ignore"), + SettingValue(types.LastClose(), "ignore"), "Behaviour when the last tab is closed."), )), ('webkit', sect.KeyValue( ('auto_load_images', - SettingValue(types.Bool, "true"), + SettingValue(types.Bool(), "true"), "Specifies whether images are automatically loaded in web pages."), ('dns_prefetch_enabled', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Specifies whether QtWebkit will try to pre-fetch DNS entries to " "speed up browsing."), ('javascript_enabled', - SettingValue(types.Bool, "true"), + SettingValue(types.Bool(), "true"), "Enables or disables the running of JavaScript programs."), #('java_enabled', - # SettingValue(types.Bool, "true"), + # SettingValue(types.Bool(), "true"), # "Enables or disables Java applets. Currently Java applets are " # "not supported"), ('plugins_enabled', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Enables or disables plugins in Web pages"), ('private_browsing_enabled', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Private browsing prevents WebKit from recording visited pages in " "the history and storing web page icons."), ('javascript_can_open_windows', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Specifies whether JavaScript programs can open new windows."), ('javascript_can_close_windows', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Specifies whether JavaScript programs can close windows."), ('javascript_can_access_clipboard', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Specifies whether JavaScript programs can read or write to the " "clipboard."), ('developer_extras_enabled', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Enables extra tools for Web developers (e.g. webinspector)"), ('spatial_navigation_enabled', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Enables or disables the Spatial Navigation feature, which consists " "in the ability to navigate between focusable elements in a Web " "page, such as hyperlinks and form controls, by using Left, Right, " "Up and Down arrow keys."), ('links_included_in_focus_chain', - SettingValue(types.Bool, "true"), + SettingValue(types.Bool(), "true"), "Specifies whether hyperlinks should be included in the keyboard " "focus chain."), ('zoom_text_only', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Specifies whether the zoom factor on a frame applies only to the " "text or to all content."), ('print_element_backgrounds', - SettingValue(types.Bool, "true"), + SettingValue(types.Bool(), "true"), "Specifies whether the background color and images are also drawn " "when the page is printed. "), ('offline_storage_database_enabled', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Specifies whether support for the HTML 5 offline storage feature is " "enabled or not. "), ('offline_web_application_storage_enabled', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Specifies whether support for the HTML 5 web application cache " "feature is enabled or not. "), ('local_storage_enabled', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Specifies whether support for the HTML 5 local storage feature is " "enabled or not."), ('local_content_can_access_remote_urls', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Specifies whether locally loaded documents are allowed to access " "remote urls."), ('local_content_can_access_file_urls', - SettingValue(types.Bool, "true"), + SettingValue(types.Bool(), "true"), "Specifies whether locally loaded documents are allowed to access " "other local urls."), ('xss_auditing_enabled', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "Specifies whether load requests should be monitored for cross-site " "scripting attempts. Suspicious scripts will be blocked and reported " "in the inspector's JavaScript console. Enabling this feature might " "have an impact on performance."), #('accelerated_compositing_enabled', - # SettingValue(types.Bool, "true"), + # SettingValue(types.Bool(), "true"), # "This feature, when used in conjunction with QGraphicsWebView, " # "accelerates animations of web content. CSS animations of the " # "transform and opacity properties will be rendered by composing the " # "cached content of the animated elements. "), #('tiled_backing_store_enabled', - # SettingValue(types.Bool, "false"), + # SettingValue(types.Bool(), "false"), # "This setting enables the tiled backing store feature for a " # "QGraphicsWebView. With the tiled backing store enabled, the web " # "page contents in and around the current visible area is " @@ -298,30 +300,30 @@ DATA = OrderedDict([ # "Enabling the feature increases memory consuption."), ('frame_flattening_enabled', - SettingValue(types.Bool, "false"), + SettingValue(types.Bool(), "false"), "With this setting each subframe is expanded to its contents. This " "will flatten all the frames to become one scrollable " "page."), ('site_specific_quirks_enabled', - SettingValue(types.Bool, "true"), + SettingValue(types.Bool(), "true"), "This setting enables WebKit's workaround for broken sites."), )), ('hints', sect.KeyValue( ('border', - SettingValue(types.String, "1px solid #E3BE23"), + SettingValue(types.String(), "1px solid #E3BE23"), "CSS border value for hints."), ('opacity', - SettingValue(types.Float, "0.7"), + SettingValue(types.Float(minval=0.0, maxval=1.0), "0.7"), "Opacity for hints."), - ('chars', - SettingValue(types.String, "asdfghjkl"), + ('chars', + SettingValue(types.String(minlen=2), "asdfghjkl"), "Chars used for hint strings."), )), ('searchengines', sect.ValueList( - types.SearchEngineName, types.SearchEngineUrl, + types.SearchEngineName(), types.SearchEngineUrl(), ('DEFAULT', '${duckduckgo}'), ('duckduckgo', 'https://duckduckgo.com/?q={}'), ('ddg', '${duckduckgo}'), @@ -333,7 +335,7 @@ DATA = OrderedDict([ )), ('keybind', sect.ValueList( - types.KeyBindingName, types.KeyBinding, + types.KeyBindingName(), types.KeyBinding(), ('o', 'open'), ('go', 'opencur'), ('O', 'tabopen'), @@ -381,140 +383,140 @@ DATA = OrderedDict([ )), ('aliases', sect.ValueList( - types.Command, types.Command, + types.Command(), types.Command(), )), ('colors', sect.KeyValue( ('completion.fg', - SettingValue(types.Color, "#333333"), + SettingValue(types.Color(), "#333333"), "Text color of the completion widget."), ('completion.item.bg', - SettingValue(types.Color, "white"), + SettingValue(types.Color(), "white"), "Background color of completion widget items."), ('completion.category.bg', - SettingValue(types.Color, "qlineargradient(x1:0, y1:0, x2:0, y2:1, " + SettingValue(types.Color(), "qlineargradient(x1:0, y1:0, x2:0, y2:1, " "stop:0 #e4e4e4, stop:1 #dbdbdb)"), "Background color of the completion widget category headers."), ('completion.category.border.top', - SettingValue(types.Color, "#808080"), + SettingValue(types.Color(), "#808080"), "Top border color of the completion widget category headers."), ('completion.category.border.bottom', - SettingValue(types.Color, "#bbbbbb"), + SettingValue(types.Color(), "#bbbbbb"), "Bottom border color of the completion widget category headers."), ('completion.item.selected.fg', - SettingValue(types.Color, "#333333"), + SettingValue(types.Color(), "#333333"), "Foreground color of the selected completion item."), ('completion.item.selected.bg', - SettingValue(types.Color, "#ffec8b"), + SettingValue(types.Color(), "#ffec8b"), "Background color of the selected completion item."), ('completion.item.selected.border.top', - SettingValue(types.Color, "#f2f2c0"), + SettingValue(types.Color(), "#f2f2c0"), "Top border color of the completion widget category headers."), ('completion.item.selected.border.bottom', - SettingValue(types.Color, "#ffec8b"), + SettingValue(types.Color(), "#ffec8b"), "Bottom border color of the selected completion item."), ('completion.match.fg', - SettingValue(types.Color, "red"), + SettingValue(types.Color(), "red"), "Foreground color of the matched text in the completion."), ('statusbar.bg', - SettingValue(types.Color, "black"), + SettingValue(types.Color(), "black"), "Foreground color of the statusbar."), ('statusbar.fg', - SettingValue(types.Color, "white"), + SettingValue(types.Color(), "white"), "Foreground color of the statusbar."), ('statusbar.bg.error', - SettingValue(types.Color, "red"), + SettingValue(types.Color(), "red"), "Background color of the statusbar if there was an error."), ('statusbar.fg.error', - SettingValue(types.Color, "${statusbar.fg}"), + SettingValue(types.Color(), "${statusbar.fg}"), "Foreground color of the statusbar if there was an error."), ('statusbar.progress.bg', - SettingValue(types.Color, "white"), + SettingValue(types.Color(), "white"), "Background color of the progress bar."), ('statusbar.url.fg', - SettingValue(types.Color, "${statusbar.fg}"), + SettingValue(types.Color(), "${statusbar.fg}"), "Default foreground color of the URL in the statusbar."), ('statusbar.url.fg.success', - SettingValue(types.Color, "lime"), + SettingValue(types.Color(), "lime"), "Foreground color of the URL in the statusbar on successful " "load."), ('statusbar.url.fg.error', - SettingValue(types.Color, "orange"), + SettingValue(types.Color(), "orange"), "Foreground color of the URL in the statusbar on error."), ('statusbar.url.fg.warn', - SettingValue(types.Color, "yellow"), + SettingValue(types.Color(), "yellow"), "Foreground color of the URL in the statusbar when there's a " "warning."), ('statusbar.url.fg.hover', - SettingValue(types.Color, "aqua"), + SettingValue(types.Color(), "aqua"), "Foreground color of the URL in the statusbar for hovered " "links."), ('tab.fg', - SettingValue(types.Color, "white"), + SettingValue(types.Color(), "white"), "Foreground color of the tabbar."), ('tab.bg', - SettingValue(types.Color, "grey"), + SettingValue(types.Color(), "grey"), "Background color of the tabbar."), ('tab.bg.selected', - SettingValue(types.Color, "black"), + SettingValue(types.Color(), "black"), "Background color of the tabbar for the selected tab."), ('tab.seperator', - SettingValue(types.Color, "white"), + SettingValue(types.Color(), "white"), "Color for the tab seperator."), ('hints.fg', - SettingValue(types.CssColor, "black"), + SettingValue(types.CssColor(), "black"), "Font color for hints."), ('hints.fg.match', - SettingValue(types.CssColor, "green"), + SettingValue(types.CssColor(), "green"), "Font color for the matched part of hints."), ('hints.bg', - SettingValue(types.CssColor, "-webkit-gradient(linear, left top, " - "left bottom, color-stop(0%,#FFF785), " - "color-stop(100%,#FFC542))"), + SettingValue(types.CssColor(), "-webkit-gradient(linear, left top, " + "left bottom, color-stop(0%,#FFF785), " + "color-stop(100%,#FFC542))"), "Background color for hints."), )), ('fonts', sect.KeyValue( ('completion', - SettingValue(types.Font, "8pt Monospace"), + SettingValue(types.Font(), "8pt Monospace"), "Font used in the completion widget."), ('tabbar', - SettingValue(types.Font, "8pt Monospace"), + SettingValue(types.Font(), "8pt Monospace"), "Font used in the tabbar."), ('statusbar', - SettingValue(types.Font, "8pt Monospace"), + SettingValue(types.Font(), "8pt Monospace"), "Font used in the statusbar."), ('hints', - SettingValue(types.Font, "bold 12px Monospace"), + SettingValue(types.Font(), "bold 12px Monospace"), "Font used for the hints."), )), ]) diff --git a/qutebrowser/config/conftypes.py b/qutebrowser/config/conftypes.py index 0a47aacbc..18aa119a0 100644 --- a/qutebrowser/config/conftypes.py +++ b/qutebrowser/config/conftypes.py @@ -113,16 +113,29 @@ class BaseType: class String(BaseType): - """Base class for a string setting (case-insensitive).""" + """Base class for a string setting (case-insensitive). + + Attributes: + minlen: Minimum length (inclusive). + maxlen: Maximum length (inclusive). + """ typestr = 'string' + def __init__(self, minlen=None, maxlen=None): + self.minlen = minlen + self.maxlen = maxlen + def transform(self, value): return value.lower() def validate(self, value): - # Nothing to do - return + if self.minlen is not None and len(value) < self.minlen: + raise ValidationError(value, "must be at least {} chars " + "long!".format(self.minlen)) + if self.maxlen is not None and len(value) > self.maxlen: + raise ValidationError(value, "must be at most {} long!".format( + self.maxlen)) class Bool(BaseType): @@ -150,34 +163,64 @@ class Bool(BaseType): class Int(BaseType): - """Base class for an integer setting.""" + """Base class for an integer setting. + + Attributes: + minval: Minimum value (inclusive). + maxval: Maximum value (inclusive). + """ typestr = 'int' + def __init__(self, minval=None, maxval=None): + self.minval = minval + self.maxval = maxval + def transform(self, value): return int(value) def validate(self, value): try: - int(value) + intval = int(value) except ValueError: raise ValidationError(value, "must be an integer!") + if self.minval is not None and intval < self.minval: + raise ValidationError(value, "must be {} or bigger!".format( + self.minval)) + if self.maxval is not None and intval > self.maxval: + raise ValidationError(value, "must be {} or smaller!".format( + self.maxval)) class Float(BaseType): - """Base class for an float setting.""" + """Base class for an float setting. + + Attributes: + minval: Minimum value (inclusive). + maxval: Maximum value (inclusive). + """ typestr = 'float' + def __init__(self, minval=None, maxval=None): + self.minval = minval + self.maxval = maxval + def transform(self, value): return float(value) def validate(self, value): try: - float(value) + floatval = float(value) except ValueError: raise ValidationError(value, "must be a float!") + if self.minval is not None and floatval < self.minval: + raise ValidationError(value, "must be {} or bigger!".format( + self.minval)) + if self.maxval is not None and floatval > self.maxval: + raise ValidationError(value, "must be {} or smaller!".format( + self.maxval)) class List(BaseType): @@ -212,7 +255,16 @@ class IntList(List): class Perc(BaseType): - """Percentage which may be >100 but needs to be positive.""" + """Percentage. + + Attributes: + minval: Minimum value (inclusive). + maxval: Maximum value (inclusive). + """ + + def __init__(self, minval=None, maxval=None): + self.minval = minval + self.maxval = maxval def transform(self, value): return int(value.rstrip('%')) @@ -224,26 +276,39 @@ class Perc(BaseType): intval = int(value.rstrip('%')) except ValueError: raise ValidationError(value, "invalid percentage!") - else: - if not intval >= 0: - raise ValidationError(value, "percentage needs to be >= 0!") + if self.minval is not None and intval < self.minval: + raise ValidationError(value, "must be {}% or more!".format( + self.minval)) + if self.maxval is not None and intval > self.maxval: + raise ValidationError(value, "must be {}% or less!".format( + self.maxval)) class PercList(List): - """Base class for a list of percentages.""" + """Base class for a list of percentages. + + Attributes: + minval: Minimum value (inclusive). + maxval: Maximum value (inclusive). + """ typestr = 'perc-list' + def __init__(self, minval=None, maxval=None): + self.minval = minval + self.maxval = maxval + def transform(self, value): vals = super().transform(value) return [int(val.rstrip('%')) for val in vals] def validate(self, value): vals = super().transform(value) + perctype = Perc(minval=self.minval, maxval=self.maxval) try: for val in vals: - Perc.validate(self, val) + perctype.validate(val) except ValidationError: raise ValidationError(value, "must be a list of percentages!") @@ -259,7 +324,20 @@ class ZoomPerc(Perc): class PercOrInt(BaseType): - """Percentage or integer.""" + """Percentage or integer. + + Attributes: + minperc: Minimum value for percentage (inclusive). + maxperc: Maximum value for percentage (inclusive). + minint: Minimum value for integer (inclusive). + maxint: Maximum value for integer (inclusive). + """ + + def __init__(self, minperc=None, maxperc=None, minint=None, maxint=None): + self.minperc = minperc + self.maxperc = maxperc + self.minint = minint + self.maxint = maxint def validate(self, value): if value.endswith('%'): @@ -267,18 +345,23 @@ class PercOrInt(BaseType): intval = int(value.rstrip('%')) except ValueError: raise ValidationError(value, "invalid percentage!") - else: - if not 0 <= intval <= 100: - raise ValidationError(value, "percentage needs to be >= 0 " - "and <= 100!") + if self.minperc is not None and intval < self.minperc: + raise ValidationError(value, "must be {}% or more!".format( + self.minperc)) + if self.maxperc is not None and intval > self.maxperc: + raise ValidationError(value, "must be {}% or less!".format( + self.maxperc)) else: try: intval = int(value) except ValueError: raise ValidationError(value, "must be integer or percentage!") - else: - if intval < 0: - raise ValidationError(value, "must be >= 0") + if self.minint is not None and intval < self.minint: + raise ValidationError(value, "must be {} or bigger!".format( + self.minint)) + if self.maxint is not None and intval > self.maxint: + raise ValidationError(value, "must be {} or smaller!".format( + self.maxint)) class Command(BaseType): diff --git a/qutebrowser/config/sections.py b/qutebrowser/config/sections.py index 3a79e42a4..e109a5272 100644 --- a/qutebrowser/config/sections.py +++ b/qutebrowser/config/sections.py @@ -149,8 +149,8 @@ class ValueList(Section): """Wrap types over default values. Take care when overriding this. Args: - keytype: The type to be used for keys. - valtype: The type to be used for values. + keytype: The type instance to be used for keys. + valtype: The type instance to be used for values. *defaults: A (key, value) list of default values. """ super().__init__() @@ -166,7 +166,7 @@ class ValueList(Section): self.layers['default']) def setv(self, layer, key, value, interpolated): - self.keytype().validate(key) + self.keytype.validate(key) if key in self.layers[layer]: self.layers[layer][key].setv(layer, value, interpolated) else: diff --git a/qutebrowser/config/value.py b/qutebrowser/config/value.py index 5a12177c5..b0a50cf4e 100644 --- a/qutebrowser/config/value.py +++ b/qutebrowser/config/value.py @@ -27,7 +27,7 @@ class SettingValue: Intended to be subclassed by config value "types". Attributes: - typ: A BaseType subclass. + typ: A BaseType subclass instance. value: (readonly property) The currently valid, most important value. _values: An OrderedDict with the values on different layers, with the most significant layer first. @@ -40,7 +40,7 @@ class SettingValue: typ: The BaseType to use. default: Raw value to set. """ - self.typ = typ() + self.typ = typ self._values = OrderedDict.fromkeys(['temp', 'conf', 'default']) self._values['default'] = default