From 0bb4e2c0f09f88f9b06cf739cef648b0cec960aa Mon Sep 17 00:00:00 2001 From: Tontyna <35614937+Tontyna@users.noreply.github.com> Date: Tue, 22 Oct 2019 21:19:17 +0200 Subject: [PATCH 1/7] Prevent improper var() from crashing with `InvalidValues` The css validation functions raise `InvalidValues`. Basic CSS validation, done in Step 2, catches and emits the exception as a WARNING. In Step 3, when a var() needs to be resolved, those validation functions are called again. We must catch the `InvalidValues` in compute(), too. Currently only the `transform` property seems to be endangered. --- weasyprint/css/computed_values.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/weasyprint/css/computed_values.py b/weasyprint/css/computed_values.py index 76d99215d..f80429069 100644 --- a/weasyprint/css/computed_values.py +++ b/weasyprint/css/computed_values.py @@ -20,8 +20,8 @@ from ..urls import get_link_attribute from .properties import INHERITED, INITIAL_VALUES, Dimension from .utils import ( - ANGLE_TO_RADIANS, LENGTH_UNITS, LENGTHS_TO_PIXELS, check_var_function, - safe_urljoin) + ANGLE_TO_RADIANS, LENGTH_UNITS, LENGTHS_TO_PIXELS, InvalidValues, + check_var_function, safe_urljoin) ZERO_PIXELS = Dimension(0, 'px') @@ -226,10 +226,14 @@ def compute(element, pseudo_type, specified, computed, parent_style, new_value = None else: prop = PROPERTIES[name.replace('_', '-')] - if prop.wants_base_url: - new_value = prop(computed_value, base_url) - else: - new_value = prop(computed_value) + # catch InvalidValues, e.g. for transform + try: + if prop.wants_base_url: + new_value = prop(computed_value, base_url) + else: + new_value = prop(computed_value) + except InvalidValues: + new_value = None # See https://drafts.csswg.org/css-variables/#invalid-variables if new_value is None: From af7817163a073236294c6d7b139899d49d324163 Mon Sep 17 00:00:00 2001 From: Tontyna <35614937+Tontyna@users.noreply.github.com> Date: Wed, 23 Oct 2019 18:05:32 +0200 Subject: [PATCH 2/7] Prevent fallback for font-size:var() from crashing Both, parent_style['font_size'] and INITIAL_VALUES['font_size'], are unitless integers. --- weasyprint/css/computed_values.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/weasyprint/css/computed_values.py b/weasyprint/css/computed_values.py index f80429069..a18550243 100644 --- a/weasyprint/css/computed_values.py +++ b/weasyprint/css/computed_values.py @@ -615,6 +615,9 @@ def font_size(computer, name, value): return keyword_values[-i - 1] else: return parent_font_size * 0.8 + elif isinstance(value, int): + # Due to improper var() the unit-less parent value can get here + return value elif value.unit == '%': return value.value * parent_font_size / 100. else: From 7fde385079d5dcf7d8105b14ed18b9849dbbebc8 Mon Sep 17 00:00:00 2001 From: Tontyna <35614937+Tontyna@users.noreply.github.com> Date: Wed, 23 Oct 2019 18:33:09 +0200 Subject: [PATCH 3/7] Prevent fallback for word-spacing:var() from crashing Both, parent_style['word_spacing'] and INITIAL_VALUES['word_spacing'], are unitless integers. --- weasyprint/css/computed_values.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/weasyprint/css/computed_values.py b/weasyprint/css/computed_values.py index a18550243..4f28d686d 100644 --- a/weasyprint/css/computed_values.py +++ b/weasyprint/css/computed_values.py @@ -737,6 +737,10 @@ def word_spacing(computer, name, value): """Compute the ``word-spacing`` property.""" if value == 'normal': return 0 + elif isinstance(value, int): + # The initial value can get here, but length() would fail as + # it does not have a 'unit' attribute. + return value else: return length(computer, name, value, pixels_only=True) From a79a42a22f67bcaabb42721fc82d82a4515bac7c Mon Sep 17 00:00:00 2001 From: Tontyna <35614937+Tontyna@users.noreply.github.com> Date: Wed, 23 Oct 2019 19:46:05 +0200 Subject: [PATCH 4/7] Prevent more fallbacks for var() from crashing Fallbacks for border-spacing and size contain unitless numbers --- weasyprint/css/computed_values.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/weasyprint/css/computed_values.py b/weasyprint/css/computed_values.py index 4f28d686d..b7adc9db4 100644 --- a/weasyprint/css/computed_values.py +++ b/weasyprint/css/computed_values.py @@ -301,7 +301,9 @@ def length_or_percentage_tuple(computer, name, values): @register_computer('clip') def length_tuple(computer, name, values): """Compute the properties with a list of lengths.""" - return tuple(length(computer, name, value, pixels_only=True) + # be aware of parent_style/initial values being plain numbers + return tuple(value if isinstance(value, (int, float)) else + length(computer, name, value, pixels_only=True) for value in values) From 11cc76567266ac719dc524d7d427b3c3d068d79a Mon Sep 17 00:00:00 2001 From: Tontyna <35614937+Tontyna@users.noreply.github.com> Date: Wed, 23 Oct 2019 21:46:20 +0200 Subject: [PATCH 5/7] Prevent some string fallbacks for var() from crashing The initial values 'none' and 'normal' of string-set resp. content crash with `UnboundLocalError` in `_content_list` unless deterred. --- weasyprint/css/computed_values.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/weasyprint/css/computed_values.py b/weasyprint/css/computed_values.py index b7adc9db4..cea6a2009 100644 --- a/weasyprint/css/computed_values.py +++ b/weasyprint/css/computed_values.py @@ -545,14 +545,21 @@ def bookmark_label(computer, name, values): def string_set(computer, name, values): """Compute the ``string-set`` property.""" # Spec asks for strings after custom keywords, but we allow content-lists - return tuple( - (string_set[0], _content_list(computer, string_set[1])) - for string_set in values) + # The initial value of 'none' can get here by improper var() + if values == 'none': + return () + else: + return tuple( + (string_set[0], _content_list(computer, string_set[1])) + for string_set in values) @register_computer('content') def content(computer, name, values): """Compute the ``content`` property.""" + # take care for INITIAL_VALUES['content'] + if values == 'normal': + return 'inhibit' if computer['pseudo_type'] else 'contents' if len(values) == 1: value, = values if value == 'normal': From d0eeebab399f2b79a8b8c8cc12e75ef82ae57de4 Mon Sep 17 00:00:00 2001 From: Tontyna <35614937+Tontyna@users.noreply.github.com> Date: Wed, 23 Oct 2019 22:41:22 +0200 Subject: [PATCH 6/7] Prevent crash caused by `None` fallback value for var() The INITIAL_VALUES for anchor, link and lang (oops, css properties!?) is None. If the fallback value is None there is no use in calling the @registered function. Just return None. --- weasyprint/css/computed_values.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/weasyprint/css/computed_values.py b/weasyprint/css/computed_values.py index cea6a2009..2a8e546dc 100644 --- a/weasyprint/css/computed_values.py +++ b/weasyprint/css/computed_values.py @@ -252,6 +252,9 @@ def compute(element, pseudo_type, specified, computed, parent_style, value = INITIAL_VALUES[name] else: value = new_value + # no value, no fallback, no need for a function call + if value is None: + function = None if function is not None: value = function(computer, name, value) From f0a432fce6e7faac3ba5a5c7b12e833b70bd81a6 Mon Sep 17 00:00:00 2001 From: Tontyna <35614937+Tontyna@users.noreply.github.com> Date: Thu, 24 Oct 2019 00:25:02 +0200 Subject: [PATCH 7/7] Add test --- weasyprint/tests/test_variables.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/weasyprint/tests/test_variables.py b/weasyprint/tests/test_variables.py index 60b89bc4d..0d2f75c3d 100644 --- a/weasyprint/tests/test_variables.py +++ b/weasyprint/tests/test_variables.py @@ -9,6 +9,7 @@ """ +from ..css.properties import KNOWN_PROPERTIES from .test_boxes import render_pages as parse @@ -95,3 +96,15 @@ def test_variable_initial(): body, = html.children paragraph, = body.children assert paragraph.width == 10 + + +def test_variable_fallback(): + parse(''' + +
+ ''' % ''.join(name + ': var(--var);\n' for name in KNOWN_PROPERTIES))