diff --git a/README b/README index e90519811..37524abc9 100644 --- a/README +++ b/README @@ -4,7 +4,7 @@ librsvg-og This is librsvg-og (read: librsvg "old gen") - A small library to render Scalable Vector Graphics (SVG). It has been created as a fork of the original librsvg of version 2.40.20, which was the last stable release untainted by rust -additions. librsvg-ng performs the same task as the original librsvg, to render +additions. librsvg-og performs the same task as the original librsvg, to render SVG files to Cairo surfaces, and should be a drop-in replacement for librsvg, assuming no API-breaking changes by upstream. diff --git a/configure.ac b/configure.ac index 3b1ebe869..35dcd47eb 100644 --- a/configure.ac +++ b/configure.ac @@ -1,11 +1,11 @@ m4_define([rsvg_major_version],[2]) m4_define([rsvg_minor_version],[40]) -m4_define([rsvg_micro_version],[20]) +m4_define([rsvg_micro_version],[21]) m4_define([rsvg_extra_version],[]) m4_define([rsvg_version],[rsvg_major_version.rsvg_minor_version.rsvg_micro_version()rsvg_extra_version]) m4_define([rsvg_lt_version_info],m4_eval(rsvg_major_version + rsvg_minor_version):rsvg_micro_version:rsvg_minor_version) -AC_INIT([RSVG],[rsvg_version],[https://bugzilla.gnome.org/enter_bug.cgi?product=librsvg],[librsvg]) +AC_INIT([RSVG],[rsvg_version],[https://github.com/oaken-source/librsvg-og/issues],[librsvg]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_SRCDIR([rsvg.h]) diff --git a/rsvg-base.c b/rsvg-base.c index b637f93bf..756f149ae 100644 --- a/rsvg-base.c +++ b/rsvg-base.c @@ -705,12 +705,36 @@ rsvg_start_xinclude (RsvgHandle * ctx, RsvgPropertyBag * atts) /* end xinclude */ +static gboolean +loading_limits_exceeded (RsvgHandle *handle) +{ + /* This is a mitigation for SVG files which create millions of elements + * in an attempt to exhaust memory. We don't allow loading more than + * this number of elements during the initial streaming load process. + */ + return handle->priv->num_loaded_elements > 200000; +} + static void rsvg_start_element (void *data, const xmlChar * name, const xmlChar ** atts) { RsvgPropertyBag *bag; RsvgHandle *ctx = (RsvgHandle *) data; + /* In a different way from librsvg 2.42, we do the following check here, not + * in rsvg_standard_element_start() as it is done there. This is because + * librsvg 2.40 still creates nodes for and <metadata> elements, and + * we'd like to prevent unbounded memory consuption for those elements, too. + */ + if (loading_limits_exceeded (ctx)) { + g_set_error (ctx->priv->error, RSVG_ERROR, 0, "instancing limit"); + + xmlStopParser (ctx->priv->ctxt); + return; + } + + ctx->priv->num_loaded_elements += 1; + bag = rsvg_property_bag_new ((const char **) atts); if (ctx->priv->handler) { @@ -1387,6 +1411,7 @@ rsvg_handle_get_dimensions_sub (RsvgHandle * handle, RsvgDimensionData * dimensi RsvgNodeSvg *root = NULL; RsvgNode *sself = NULL; RsvgBbox bbox; + gboolean retval = FALSE; gboolean handle_subelement = TRUE; @@ -1446,6 +1471,12 @@ rsvg_handle_get_dimensions_sub (RsvgHandle * handle, RsvgDimensionData * dimensi rsvg_node_draw (handle->priv->treebase, draw, 0); bbox = RSVG_CAIRO_RENDER (draw->render)->bbox; + if (rsvg_drawing_ctx_limits_exceeded (draw)) { + retval = FALSE; + } else { + retval = TRUE; + } + cairo_restore (cr); rsvg_state_pop (draw); rsvg_drawing_ctx_free (draw); @@ -1463,6 +1494,8 @@ rsvg_handle_get_dimensions_sub (RsvgHandle * handle, RsvgDimensionData * dimensi dimension_data->height = (int) (_rsvg_css_hand_normalize_length (&root->h, handle->priv->dpi_y, bbox.rect.height + bbox.rect.y * 2, 12) + 0.5); + + retval = TRUE; } dimension_data->em = dimension_data->width; @@ -1472,7 +1505,7 @@ rsvg_handle_get_dimensions_sub (RsvgHandle * handle, RsvgDimensionData * dimensi (*handle->priv->size_func) (&dimension_data->width, &dimension_data->height, handle->priv->user_data); - return TRUE; + return retval; } /** @@ -1498,7 +1531,7 @@ rsvg_handle_get_position_sub (RsvgHandle * handle, RsvgPositionData * position_d RsvgDimensionData dimension_data; cairo_surface_t *target = NULL; cairo_t *cr = NULL; - gboolean ret = FALSE; + gboolean retval = FALSE; g_return_val_if_fail (handle, FALSE); g_return_val_if_fail (position_data, FALSE); @@ -1544,6 +1577,12 @@ rsvg_handle_get_position_sub (RsvgHandle * handle, RsvgPositionData * position_d rsvg_node_draw (handle->priv->treebase, draw, 0); bbox = RSVG_CAIRO_RENDER (draw->render)->bbox; + if (rsvg_drawing_ctx_limits_exceeded (draw)) { + retval = FALSE; + } else { + retval = TRUE; + } + cairo_restore (cr); rsvg_state_pop (draw); rsvg_drawing_ctx_free (draw); @@ -1560,15 +1599,13 @@ rsvg_handle_get_position_sub (RsvgHandle * handle, RsvgPositionData * position_d (*handle->priv->size_func) (&dimension_data.width, &dimension_data.height, handle->priv->user_data); - ret = TRUE; - bail: if (cr) cairo_destroy (cr); if (target) cairo_surface_destroy (target); - return ret; + return retval; } /** @@ -2166,6 +2203,52 @@ rsvg_push_discrete_layer (RsvgDrawingCtx * ctx) ctx->render->push_discrete_layer (ctx); } +void +rsvg_drawing_ctx_increase_num_elements_acquired (RsvgDrawingCtx *draw_ctx) +{ + draw_ctx->num_elements_acquired++; +} + +/* This is a mitigation for the security-related bugs: + * https://gitlab.gnome.org/GNOME/librsvg/issues/323 + * https://gitlab.gnome.org/GNOME/librsvg/issues/515 + * + * Imagine the XML [billion laughs attack], but done in SVG's terms: + * + * - #323 above creates deeply nested groups of `<use>` elements. + * The first one references the second one ten times, the second one + * references the third one ten times, and so on. In the file given, + * this causes 10^17 objects to be rendered. While this does not + * exhaust memory, it would take a really long time. + * + * - #515 has deeply nested references of `<pattern>` elements. Each + * object inside each pattern has an attribute + * fill="url(#next_pattern)", so the number of final rendered objects + * grows exponentially. + * + * We deal with both cases by placing a limit on how many references + * will be resolved during the SVG rendering process, that is, + * how many `url(#foo)` will be resolved. + * + * [billion laughs attack]: https://bitbucket.org/tiran/defusedxml + */ +gboolean +rsvg_drawing_ctx_limits_exceeded (RsvgDrawingCtx *draw_ctx) +{ + return draw_ctx->num_elements_acquired > 500000; +} + +RsvgNode * +rsvg_drawing_ctx_acquire_node_ref (RsvgDrawingCtx * ctx, RsvgNode *node) +{ + if (g_slist_find (ctx->acquired_nodes, node)) + return NULL; + + ctx->acquired_nodes = g_slist_prepend (ctx->acquired_nodes, node); + + return node; +} + /* * rsvg_acquire_node: * @ctx: The drawing context in use @@ -2186,16 +2269,18 @@ rsvg_acquire_node (RsvgDrawingCtx * ctx, const char *url) { RsvgNode *node; + if (url == NULL) + return NULL; + + rsvg_drawing_ctx_increase_num_elements_acquired (ctx); + if (rsvg_drawing_ctx_limits_exceeded (ctx)) + return NULL; + node = rsvg_defs_lookup (ctx->defs, url); if (node == NULL) return NULL; - if (g_slist_find (ctx->acquired_nodes, node)) - return NULL; - - ctx->acquired_nodes = g_slist_prepend (ctx->acquired_nodes, node); - - return node; + return rsvg_drawing_ctx_acquire_node_ref (ctx, node); } /* diff --git a/rsvg-cairo-render.c b/rsvg-cairo-render.c index 19ba53270..1f4b08568 100644 --- a/rsvg-cairo-render.c +++ b/rsvg-cairo-render.c @@ -174,6 +174,7 @@ rsvg_cairo_new_drawing_ctx (cairo_t * cr, RsvgHandle * handle) draw->dpi_y = handle->priv->dpi_y; draw->vb.rect.width = data.em; draw->vb.rect.height = data.ex; + draw->num_elements_acquired = 0; draw->pango_context = NULL; draw->drawsub_stack = NULL; draw->acquired_nodes = NULL; @@ -219,6 +220,7 @@ rsvg_handle_render_cairo_sub (RsvgHandle * handle, cairo_t * cr, const char *id) { RsvgDrawingCtx *draw; RsvgNode *drawsub = NULL; + gboolean retval = FALSE; g_return_val_if_fail (handle != NULL, FALSE); @@ -247,11 +249,17 @@ rsvg_handle_render_cairo_sub (RsvgHandle * handle, cairo_t * cr, const char *id) rsvg_node_draw ((RsvgNode *) handle->priv->treebase, draw, 0); + if (rsvg_drawing_ctx_limits_exceeded (draw)) { + retval = FALSE; + } else { + retval = TRUE; + } + cairo_restore (cr); rsvg_state_pop (draw); rsvg_drawing_ctx_free (draw); - return TRUE; + return retval; } /** diff --git a/rsvg-private.h b/rsvg-private.h index 8c3083dc2..82157bc21 100644 --- a/rsvg-private.h +++ b/rsvg-private.h @@ -167,6 +167,7 @@ struct RsvgHandlePrivate { */ RsvgSaxHandler *handler; int handler_nest; + gsize num_loaded_elements; GHashTable *entities; /* g_malloc'd string -> xmlEntityPtr */ @@ -203,6 +204,7 @@ struct RsvgDrawingCtx { RsvgState *state; GError **error; RsvgDefs *defs; + gsize num_elements_acquired; PangoContext *pango_context; double dpi_x, dpi_y; RsvgViewBox vb; @@ -369,6 +371,10 @@ void rsvg_pop_discrete_layer (RsvgDrawingCtx * ctx); G_GNUC_INTERNAL void rsvg_push_discrete_layer (RsvgDrawingCtx * ctx); G_GNUC_INTERNAL +gboolean rsvg_drawing_ctx_limits_exceeded (RsvgDrawingCtx *draw_ctx); +G_GNUC_INTERNAL +RsvgNode *rsvg_drawing_ctx_acquire_node_ref (RsvgDrawingCtx * ctx, RsvgNode *node); +G_GNUC_INTERNAL RsvgNode *rsvg_acquire_node (RsvgDrawingCtx * ctx, const char *url); G_GNUC_INTERNAL void rsvg_release_node (RsvgDrawingCtx * ctx, RsvgNode *node); diff --git a/rsvg-structure.c b/rsvg-structure.c index 3c972e50a..88b7ce254 100644 --- a/rsvg-structure.c +++ b/rsvg-structure.c @@ -40,6 +40,9 @@ rsvg_node_draw (RsvgNode * self, RsvgDrawingCtx * ctx, int dominate) RsvgState *state; GSList *stacksave; + if (rsvg_drawing_ctx_limits_exceeded (ctx)) + return; + state = self->state; stacksave = ctx->drawsub_stack; @@ -159,27 +162,11 @@ rsvg_node_group_pack (RsvgNode * self, RsvgNode * child) child->parent = self; } -static gboolean -rsvg_node_is_ancestor (RsvgNode * potential_ancestor, RsvgNode * potential_descendant) -{ - /* work our way up the family tree */ - while (TRUE) { - if (potential_ancestor == potential_descendant) - return TRUE; - else if (potential_descendant->parent == NULL) - return FALSE; - else - potential_descendant = potential_descendant->parent; - } - - g_assert_not_reached (); - return FALSE; -} - static void rsvg_node_use_draw (RsvgNode * self, RsvgDrawingCtx * ctx, int dominate) { RsvgNodeUse *use = (RsvgNodeUse *) self; + RsvgNode *self_acquired = NULL; RsvgNode *child; RsvgState *state; cairo_matrix_t affine; @@ -191,14 +178,26 @@ rsvg_node_use_draw (RsvgNode * self, RsvgDrawingCtx * ctx, int dominate) rsvg_state_reinherit_top (ctx, self->state, dominate); - if (use->link == NULL) - return; + /* <use> is an element that is used directly, unlike + * <pattern>, which is used through a fill="url(#...)" + * reference. However, <use> will always reference another + * element, potentially itself or an ancestor of itself (or + * another <use> which references the first one, etc.). So, + * we acquire the <use> element itself so that circular + * references can be caught. + */ + self_acquired = rsvg_drawing_ctx_acquire_node_ref (ctx, self); + if (!self_acquired) { + goto out; + } + + if (use->link == NULL) { + goto out; + } + child = rsvg_acquire_node (ctx, use->link); - if (!child) - return; - else if (rsvg_node_is_ancestor (child, self)) { /* or, if we're <use>'ing ourself */ - rsvg_release_node (ctx, child); - return; + if (!child) { + goto out; } state = rsvg_current_state (ctx); @@ -251,6 +250,12 @@ rsvg_node_use_draw (RsvgNode * self, RsvgDrawingCtx * ctx, int dominate) rsvg_release_node (ctx, child); } + +out: + + if (self_acquired) { + rsvg_release_node (ctx, self_acquired); + } } static void diff --git a/tests/Makefile.am b/tests/Makefile.am index de1e6198f..637988c6a 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,12 +1,14 @@ include $(top_srcdir)/glib-tap.mk +# Keep "errors" at the end; they are the slowest to run test_programs = \ loading \ rsvg-test \ crash \ styles \ render-crash \ - dimensions + dimensions \ + errors test_utils_common_sources = \ test-utils.c \ @@ -16,6 +18,10 @@ rsvg_test_SOURCES = \ rsvg-test.c \ $(test_utils_common_sources) +errors_SOURCES = \ + errors.c \ + $(test_utils_common_sources) + crash_SOURCES = \ crash.c \ $(test_utils_common_sources) @@ -51,6 +57,7 @@ dist_installed_test_data = \ $(wildcard $(srcdir)/resources/*) \ $(wildcard $(srcdir)/fixtures/crash/*.svg) \ $(wildcard $(srcdir)/fixtures/crash/*.png) \ + $(wildcard $(srcdir)/fixtures/errors/*) \ $(wildcard $(srcdir)/fixtures/loading/*) \ $(wildcard $(srcdir)/fixtures/reftests/*.svg) \ $(wildcard $(srcdir)/fixtures/reftests/*.png) \ diff --git a/tests/errors.c b/tests/errors.c new file mode 100644 index 000000000..1eaf86c88 --- /dev/null +++ b/tests/errors.c @@ -0,0 +1,100 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=4 nowrap ai expandtab sw=4: */ + +#include "config.h" + +#include <stdio.h> +#include <glib.h> + +#include "rsvg.h" +#include "test-utils.h" + +/* These tests are meant to test the error handlers in librsvg. As of 2.44.x we + * don't have a public API that can actually report detailed errors; we just + * report a boolean success value from the rendering functions. In time, we can + * add a richer API and test for specific errors here. + */ + +static char * +get_test_filename (const char *basename) { + return g_build_filename (test_utils_get_test_data_path (), + "errors", + basename, + NULL); +} + +static void +test_loading_error (gconstpointer data) +{ + const char *basename = data; + char *filename = get_test_filename (basename); + RsvgHandle *handle; + GError *error = NULL; + + handle = rsvg_handle_new_from_file (filename, &error); + g_free (filename); + + g_assert (handle == NULL); + g_assert (g_error_matches (error, RSVG_ERROR, RSVG_ERROR_FAILED)); + + g_error_free (error); +} + +static void +test_instancing_limit (gconstpointer data) +{ + const char *basename = data; + char *filename = get_test_filename (basename); + RsvgHandle *handle; + GError *error = NULL; + cairo_surface_t *surf; + cairo_t *cr; + + handle = rsvg_handle_new_from_file (filename, &error); + g_free (filename); + g_assert (handle != NULL); + g_assert (error == NULL); + + surf = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 11); + cr = cairo_create (surf); + + g_assert (!rsvg_handle_render_cairo (handle, cr)); + + g_object_unref (handle); +} + +int +main (int argc, char **argv) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add_data_func_full ("/errors/instancing_limit/323-nested-use.svg", + "323-nested-use.svg", + test_instancing_limit, + NULL); + + g_test_add_data_func_full ("/errors/instancing_limit/515-pattern-billion-laughs.svg", + "515-pattern-billion-laughs.svg", + test_instancing_limit, + NULL); + + g_test_add_data_func_full ("/errors/instancing_limit/308-use-self-ref.svg", + "308-use-self-ref.svg", + test_instancing_limit, + NULL); + g_test_add_data_func_full ("/errors/instancing_limit/308-recursive-use.svg", + "308-recursive-use.svg", + test_instancing_limit, + NULL); + g_test_add_data_func_full ("/errors/instancing_limit/308-doubly-recursive-use.svg", + "308-doubly-recursive-use.svg", + test_instancing_limit, + NULL); + + g_test_add_data_func_full ("/errors/515-too-many-elements.svgz", + "515-too-many-elements.svgz", + test_loading_error, + NULL); + + return g_test_run (); +} diff --git a/tests/fixtures/errors/308-doubly-recursive-use.svg b/tests/fixtures/errors/308-doubly-recursive-use.svg new file mode 100644 index 000000000..9b248a6fb --- /dev/null +++ b/tests/fixtures/errors/308-doubly-recursive-use.svg @@ -0,0 +1,13 @@ +<svg> + <defs> + <g id="one"> + <use xlink:href="#two"/> + </g> + + <g id="two"> + <use xlink:href="#one"/> + </g> + </defs> + + <use xlink:href="#one"/> +</svg> diff --git a/tests/fixtures/errors/308-recursive-use.svg b/tests/fixtures/errors/308-recursive-use.svg new file mode 100644 index 000000000..f5d00bf29 --- /dev/null +++ b/tests/fixtures/errors/308-recursive-use.svg @@ -0,0 +1,9 @@ +<svg> + <defs> + <g id="one"> + <use xlink:href="#one"/> + </g> + </defs> + + <use xlink:href="#one"/> +</svg> diff --git a/tests/fixtures/errors/308-use-self-ref.svg b/tests/fixtures/errors/308-use-self-ref.svg new file mode 100644 index 000000000..dbf14c54c --- /dev/null +++ b/tests/fixtures/errors/308-use-self-ref.svg @@ -0,0 +1,7 @@ +<svg> + <defs> + <use id="one" xlink:href="#one"/> + </defs> + + <use xlink:href="#one"/> +</svg> diff --git a/tests/fixtures/errors/323-nested-use.svg b/tests/fixtures/errors/323-nested-use.svg new file mode 100644 index 000000000..075b48eb8 --- /dev/null +++ b/tests/fixtures/errors/323-nested-use.svg @@ -0,0 +1,196 @@ +<?xml version="1.0" ?> +<svg height="600" width="600" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs > + <g id="l0"> + <rect width="600" height="600" fill="black"/> + </g> + <g id="l1"> + <use xlink:href="#l0" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l0" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l0" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l0" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l0" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l0" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l0" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l0" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l0" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l2"> + <use xlink:href="#l1" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l1" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l1" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l1" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l1" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l1" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l1" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l1" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l1" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l3"> + <use xlink:href="#l2" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l2" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l2" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l2" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l2" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l2" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l2" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l2" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l2" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l4"> + <use xlink:href="#l3" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l3" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l3" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l3" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l3" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l3" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l3" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l3" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l3" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l5"> + <use xlink:href="#l4" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l4" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l4" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l4" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l4" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l4" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l4" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l4" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l4" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l6"> + <use xlink:href="#l5" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l5" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l5" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l5" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l5" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l5" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l5" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l5" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l5" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l7"> + <use xlink:href="#l6" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l6" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l6" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l6" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l6" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l6" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l6" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l6" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l6" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l8"> + <use xlink:href="#l7" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l7" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l7" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l7" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l7" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l7" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l7" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l7" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l7" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l9"> + <use xlink:href="#l8" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l8" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l8" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l8" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l8" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l8" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l8" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l8" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l8" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l10"> + <use xlink:href="#l9" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l9" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l9" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l9" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l9" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l9" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l9" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l9" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l9" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l11"> + <use xlink:href="#l10" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l10" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l10" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l10" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l10" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l10" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l10" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l10" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l10" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l12"> + <use xlink:href="#l11" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l11" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l11" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l11" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l11" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l11" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l11" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l11" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l11" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l13"> + <use xlink:href="#l12" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l12" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l12" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l12" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l12" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l12" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l12" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l12" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l12" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l14"> + <use xlink:href="#l13" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l13" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l13" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l13" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l13" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l13" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l13" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l13" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l13" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l15"> + <use xlink:href="#l14" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l14" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l14" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l14" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l14" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l14" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l14" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l14" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l14" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l16"> + <use xlink:href="#l15" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l15" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l15" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l15" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l15" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l15" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l15" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l15" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l15" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + <g id="l17"> + <use xlink:href="#l16" transform="translate(0,0) scale(0.3333333333333333)"/> + <use xlink:href="#l16" transform="translate(200,0) scale(0.3333333333333333)"/> + <use xlink:href="#l16" transform="translate(0,200) scale(0.3333333333333333)"/> + <use xlink:href="#l16" transform="translate(200,200) scale(0.3333333333333333)"/> + <use xlink:href="#l16" transform="translate(400,200) scale(0.3333333333333333)"/> + <use xlink:href="#l16" transform="translate(0,400) scale(0.3333333333333333)"/> + <use xlink:href="#l16" transform="translate(200,400) scale(0.3333333333333333)"/> + <use xlink:href="#l16" transform="translate(400,400) scale(0.3333333333333333)"/> + <use xlink:href="#l16" transform="translate(500,0) rotate(45) scale(0.233)"/> + </g> + </defs> + <use xlink:href="#l17" transform="scale(1)"/> +</svg> diff --git a/tests/fixtures/errors/515-pattern-billion-laughs.svg b/tests/fixtures/errors/515-pattern-billion-laughs.svg new file mode 100644 index 000000000..1cb6cbe8c --- /dev/null +++ b/tests/fixtures/errors/515-pattern-billion-laughs.svg @@ -0,0 +1,130 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg width="40cm" height="20cm" viewBox="0 0 800 400" version="1.1" + xmlns="http://www.w3.org/2000/svg"> + <defs> + <pattern id="z" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(10,10)"> + <rect x="0" y="0" width="20" height="20" fill="url(#i)" stroke="yellow"/> + </pattern> + + <pattern id="i" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(0.5,0.5)"> + <rect x="0" y="0" width="20" height="20" fill="url(#h)" stroke="green" /> + <rect x="1" y="1" width="20" height="20" fill="url(#h)" stroke="brown" /> + <rect x="2" y="2" width="20" height="20" fill="url(#h)" stroke="pink" /> + <rect x="3" y="3" width="20" height="20" fill="url(#h)" stroke="grey" /> + <rect x="4" y="3" width="20" height="20" fill="url(#h)" stroke="cyan" /> + <rect x="5" y="3" width="20" height="20" fill="url(#h)" stroke="green" /> + <rect x="6" y="3" width="20" height="20" fill="url(#h)" stroke="brown" /> + <rect x="7" y="3" width="20" height="20" fill="url(#h)" stroke="pink" /> + <rect x="8" y="3" width="20" height="20" fill="url(#h)" stroke="grey" /> + <rect x="9" y="3" width="20" height="20" fill="url(#h)" stroke="cyan" /> + </pattern> + + <pattern id="h" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(0.5,0.5)"> + <rect x="0" y="0" width="20" height="20" fill="url(#g)" stroke="green" /> + <rect x="1" y="1" width="20" height="20" fill="url(#g)" stroke="brown" /> + <rect x="2" y="2" width="20" height="20" fill="url(#g)" stroke="pink" /> + <rect x="3" y="3" width="20" height="20" fill="url(#g)" stroke="grey" /> + <rect x="4" y="3" width="20" height="20" fill="url(#g)" stroke="cyan" /> + <rect x="5" y="3" width="20" height="20" fill="url(#g)" stroke="green" /> + <rect x="6" y="3" width="20" height="20" fill="url(#g)" stroke="brown" /> + <rect x="7" y="3" width="20" height="20" fill="url(#g)" stroke="pink" /> + <rect x="8" y="3" width="20" height="20" fill="url(#g)" stroke="grey" /> + <rect x="9" y="3" width="20" height="20" fill="url(#g)" stroke="cyan" /> + </pattern> + + <pattern id="g" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(0.5,0.5)"> + <rect x="0" y="0" width="20" height="20" fill="url(#f)" stroke="green" /> + <rect x="1" y="1" width="20" height="20" fill="url(#f)" stroke="brown" /> + <rect x="2" y="2" width="20" height="20" fill="url(#f)" stroke="pink" /> + <rect x="3" y="3" width="20" height="20" fill="url(#f)" stroke="grey" /> + <rect x="4" y="3" width="20" height="20" fill="url(#f)" stroke="cyan" /> + <rect x="5" y="3" width="20" height="20" fill="url(#f)" stroke="green" /> + <rect x="6" y="3" width="20" height="20" fill="url(#f)" stroke="brown" /> + <rect x="7" y="3" width="20" height="20" fill="url(#f)" stroke="pink" /> + <rect x="8" y="3" width="20" height="20" fill="url(#f)" stroke="grey" /> + <rect x="9" y="3" width="20" height="20" fill="url(#f)" stroke="cyan" /> + </pattern> + + <pattern id="f" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(0.5,0.5)"> + <rect x="0" y="0" width="20" height="20" fill="url(#e)" stroke="green" /> + <rect x="1" y="1" width="20" height="20" fill="url(#e)" stroke="brown" /> + <rect x="2" y="2" width="20" height="20" fill="url(#e)" stroke="pink" /> + <rect x="3" y="3" width="20" height="20" fill="url(#e)" stroke="grey" /> + <rect x="4" y="3" width="20" height="20" fill="url(#e)" stroke="cyan" /> + <rect x="5" y="3" width="20" height="20" fill="url(#e)" stroke="green" /> + <rect x="6" y="3" width="20" height="20" fill="url(#e)" stroke="brown" /> + <rect x="7" y="3" width="20" height="20" fill="url(#e)" stroke="pink" /> + <rect x="8" y="3" width="20" height="20" fill="url(#e)" stroke="grey" /> + <rect x="9" y="3" width="20" height="20" fill="url(#e)" stroke="cyan" /> + </pattern> + + <pattern id="e" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(0.5,0.5)"> + <rect x="0" y="0" width="20" height="20" fill="url(#d)" stroke="green" /> + <rect x="1" y="1" width="20" height="20" fill="url(#d)" stroke="brown" /> + <rect x="2" y="2" width="20" height="20" fill="url(#d)" stroke="pink" /> + <rect x="3" y="3" width="20" height="20" fill="url(#d)" stroke="grey" /> + <rect x="4" y="3" width="20" height="20" fill="url(#d)" stroke="cyan" /> + <rect x="5" y="3" width="20" height="20" fill="url(#d)" stroke="green" /> + <rect x="6" y="3" width="20" height="20" fill="url(#d)" stroke="brown" /> + <rect x="7" y="3" width="20" height="20" fill="url(#d)" stroke="pink" /> + <rect x="8" y="3" width="20" height="20" fill="url(#d)" stroke="grey" /> + <rect x="9" y="3" width="20" height="20" fill="url(#d)" stroke="cyan" /> + </pattern> + + <pattern id="d" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(0.5,0.5)"> + <rect x="0" y="0" width="20" height="20" fill="url(#c)" stroke="green" /> + <rect x="1" y="1" width="20" height="20" fill="url(#c)" stroke="brown" /> + <rect x="2" y="2" width="20" height="20" fill="url(#c)" stroke="pink" /> + <rect x="3" y="3" width="20" height="20" fill="url(#c)" stroke="grey" /> + <rect x="4" y="3" width="20" height="20" fill="url(#c)" stroke="cyan" /> + <rect x="5" y="3" width="20" height="20" fill="url(#c)" stroke="green" /> + <rect x="6" y="3" width="20" height="20" fill="url(#c)" stroke="brown" /> + <rect x="7" y="3" width="20" height="20" fill="url(#c)" stroke="pink" /> + <rect x="8" y="3" width="20" height="20" fill="url(#c)" stroke="grey" /> + <rect x="9" y="3" width="20" height="20" fill="url(#c)" stroke="cyan" /> + </pattern> + <pattern id="c" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(0.5,0.5)"> + <rect x="0" y="0" width="20" height="20" fill="url(#b)" stroke="green" /> + <rect x="1" y="1" width="20" height="20" fill="url(#b)" stroke="brown" /> + <rect x="2" y="2" width="20" height="20" fill="url(#b)" stroke="pink" /> + <rect x="3" y="3" width="20" height="20" fill="url(#b)" stroke="grey" /> + <rect x="4" y="3" width="20" height="20" fill="url(#b)" stroke="cyan" /> + <rect x="5" y="3" width="20" height="20" fill="url(#b)" stroke="green" /> + <rect x="6" y="3" width="20" height="20" fill="url(#b)" stroke="brown" /> + <rect x="7" y="3" width="20" height="20" fill="url(#b)" stroke="pink" /> + <rect x="8" y="3" width="20" height="20" fill="url(#b)" stroke="grey" /> + <rect x="9" y="3" width="20" height="20" fill="url(#b)" stroke="cyan" /> + </pattern> + <pattern id="b" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(0.5,0.5)"> + <rect x="0" y="0" width="20" height="20" fill="url(#a)" stroke="green" /> + <rect x="1" y="1" width="20" height="20" fill="url(#a)" stroke="brown" /> + <rect x="2" y="2" width="20" height="20" fill="url(#a)" stroke="pink" /> + <rect x="3" y="3" width="20" height="20" fill="url(#a)" stroke="grey" /> + <rect x="4" y="3" width="20" height="20" fill="url(#a)" stroke="cyan" /> + <rect x="5" y="3" width="20" height="20" fill="url(#a)" stroke="green" /> + <rect x="6" y="3" width="20" height="20" fill="url(#a)" stroke="brown" /> + <rect x="7" y="3" width="20" height="20" fill="url(#a)" stroke="pink" /> + <rect x="8" y="3" width="20" height="20" fill="url(#a)" stroke="grey" /> + <rect x="9" y="3" width="20" height="20" fill="url(#a)" stroke="cyan" /> + + </pattern> + <pattern id="a" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(0.5,0.5)"> + <rect x="0" y="0" width="20" height="20" fill="none" stroke="green" /> + <rect x="1" y="1" width="20" height="20" fill="none" stroke="brown" /> + <rect x="2" y="2" width="20" height="20" fill="none" stroke="pink" /> + <rect x="3" y="3" width="20" height="20" fill="none" stroke="grey" /> + <rect x="4" y="3" width="20" height="20" fill="none" stroke="cyan" /> + <rect x="5" y="3" width="20" height="20" fill="none" stroke="green" /> + <rect x="6" y="3" width="20" height="20" fill="none" stroke="brown" /> + <rect x="7" y="3" width="20" height="20" fill="none" stroke="pink" /> + <rect x="8" y="3" width="20" height="20" fill="none" stroke="grey" /> + <rect x="9" y="3" width="20" height="20" fill="none" stroke="cyan" /> + </pattern> + </defs> + + <ellipse fill="url(#z)" stroke="black" stroke-width="5" + cx="400" cy="200" rx="350" ry="150" /> + +</svg> \ No newline at end of file diff --git a/tests/fixtures/errors/515-too-many-elements.svgz b/tests/fixtures/errors/515-too-many-elements.svgz new file mode 100644 index 000000000..a7f7cf678 Binary files /dev/null and b/tests/fixtures/errors/515-too-many-elements.svgz differ