Skip to main content

rsvg/
document.rs

1//! Main SVG document structure.
2
3use data_url::mime::Mime;
4use glib::prelude::*;
5use markup5ever::QualName;
6use std::cell::Cell;
7use std::collections::HashMap;
8use std::collections::hash_map::Entry;
9use std::fmt;
10use std::include_str;
11use std::io::Cursor;
12use std::rc::Rc;
13use std::str::FromStr;
14use std::sync::Arc;
15use std::{cell::RefCell, sync::OnceLock};
16
17use crate::accept_language::UserLanguage;
18use crate::bbox::BoundingBox;
19use crate::borrow_element_as;
20use crate::css::{self, Origin, Stylesheet};
21use crate::dpi::Dpi;
22use crate::drawing_ctx::{
23    DrawingMode, RenderingConfiguration, SvgNesting, draw_tree, with_saved_cr,
24};
25use crate::error::{
26    AcquireError, InternalRenderingError, LoadingDepthError, LoadingError, NodeIdError,
27};
28use crate::io::{self, BinaryData};
29use crate::is_element_of_type;
30use crate::limits;
31use crate::node::{CascadedValues, Node, NodeBorrow, NodeData};
32use crate::rect::Rect;
33use crate::rsvg_log;
34use crate::session::Session;
35use crate::structure::IntrinsicDimensions;
36use crate::surface_utils::shared_surface::SharedImageSurface;
37use crate::url_resolver::{AllowedUrl, UrlResolver};
38use crate::xml::{Attributes, xml_load_from_possibly_compressed_stream};
39
40/// Identifier of a node
41#[derive(Debug, PartialEq, Clone)]
42pub enum NodeId {
43    /// element id
44    Internal(String),
45    /// url, element id
46    External(String, String),
47}
48
49impl NodeId {
50    pub fn parse(href: &str) -> Result<NodeId, NodeIdError> {
51        let (url, id) = match href.rfind('#') {
52            None => (Some(href), None),
53            Some(0) => (None, Some(&href[1..])),
54            Some(p) => (Some(&href[..p]), Some(&href[(p + 1)..])),
55        };
56
57        match (url, id) {
58            (None, Some(id)) if !id.is_empty() => Ok(NodeId::Internal(String::from(id))),
59            (Some(url), Some(id)) if !id.is_empty() => {
60                Ok(NodeId::External(String::from(url), String::from(id)))
61            }
62            _ => Err(NodeIdError::NodeIdRequired),
63        }
64    }
65}
66
67impl fmt::Display for NodeId {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        match self {
70            NodeId::Internal(id) => write!(f, "#{id}"),
71            NodeId::External(url, id) => write!(f, "{url}#{id}"),
72        }
73    }
74}
75
76/// Loading options for SVG documents.
77pub struct LoadOptions {
78    /// Load url resolver; all references will be resolved with respect to this.
79    pub url_resolver: UrlResolver,
80
81    /// Whether to turn off size limits in libxml2 and the image crate.
82    pub unlimited_size: bool,
83
84    /// Whether to keep original (undecoded) image data to embed in Cairo PDF surfaces.
85    pub keep_image_data: bool,
86}
87
88impl LoadOptions {
89    /// Creates a `LoadOptions` with defaults, and sets the `url resolver`.
90    pub fn new(url_resolver: UrlResolver) -> Self {
91        LoadOptions {
92            url_resolver,
93            unlimited_size: false,
94            keep_image_data: false,
95        }
96    }
97
98    /// Sets whether libxml2's and the image crate's memory usage limits should be turned off.
99    ///
100    /// This should only be done for trusted data.
101    pub fn with_unlimited_size(mut self, unlimited: bool) -> Self {
102        self.unlimited_size = unlimited;
103        self
104    }
105
106    /// Sets whether to keep the original compressed image data from referenced JPEG/PNG images.
107    ///
108    /// This is only useful for rendering to Cairo PDF
109    /// surfaces, which can embed the original, compressed image data instead of uncompressed
110    /// RGB buffers.
111    pub fn keep_image_data(mut self, keep: bool) -> Self {
112        self.keep_image_data = keep;
113        self
114    }
115
116    /// Creates a new `LoadOptions` with a different `url resolver`.
117    ///
118    /// This is used when loading a referenced file that may in turn cause other files
119    /// to be loaded, for example `<image xlink:href="subimage.svg"/>`
120    pub fn copy_with_base_url(&self, base_url: &AllowedUrl) -> Self {
121        let mut url_resolver = self.url_resolver.clone();
122        url_resolver.base_url = Some((**base_url).clone());
123
124        LoadOptions {
125            url_resolver,
126            unlimited_size: self.unlimited_size,
127            keep_image_data: self.keep_image_data,
128        }
129    }
130}
131
132/// Document-level rendering options.
133///
134/// This gets then converted to a [`RenderingConfiguration`] when all the parameters are
135/// known.
136pub struct RenderingOptions {
137    pub dpi: Dpi,
138    pub cancellable: Option<gio::Cancellable>,
139    pub user_language: UserLanguage,
140    pub svg_nesting: SvgNesting,
141    pub testing: bool,
142}
143
144impl RenderingOptions {
145    /// Copies the options to a [`RenderingConfiguration`], and adds the `measuring` flag.
146    fn to_rendering_configuration(&self, measuring: bool) -> RenderingConfiguration {
147        RenderingConfiguration {
148            dpi: self.dpi,
149            cancellable: self.cancellable.clone(),
150            user_language: self.user_language.clone(),
151            svg_nesting: self.svg_nesting,
152            testing: self.testing,
153            measuring,
154        }
155    }
156}
157
158/// A loaded SVG file and its derived data.
159pub struct Document {
160    /// Tree of nodes; the root is guaranteed to be an `<svg>` element.
161    ///
162    /// This is inside a [`RefCell`] because when cascading lazily, we
163    /// need a mutable tree.
164    tree: RefCell<Node>,
165
166    /// Metadata about the SVG handle.
167    session: Session,
168
169    /// Mapping from `id` attributes to nodes.
170    ids: HashMap<String, Node>,
171
172    /// Othewr SVG documents and images referenced from this document.
173    ///
174    /// This requires requires interior mutability because we load resources all over the
175    /// place.  Eventually we'll be able to do this once, at loading time, and keep this
176    /// immutable.
177    resources: RefCell<Resources>,
178
179    /// Used to load referenced resources.
180    load_options: Arc<LoadOptions>,
181
182    /// Stylesheets defined in the document.
183    stylesheets: Vec<Stylesheet>,
184
185    /// Whether there's a pending cascade operation.
186    ///
187    /// The document starts un-cascaded and with this flag turned on,
188    /// to avoid a double cascade if
189    /// [`crate::SvgHandle::set_stylesheet`] is called after loading
190    /// the document.
191    needs_cascade: Cell<bool>,
192}
193
194impl Document {
195    /// Constructs a `Document` by loading it from a stream.
196    ///
197    /// Note that the document is **not** cascaded just after loading.  Cascading is done lazily;
198    /// call [`Document::ensure_is_cascaded`] if you need a cascaded tree of elements.
199    pub fn load_from_stream(
200        session: Session,
201        load_options: Arc<LoadOptions>,
202        load_limiter: LoadingDepthLimiter,
203        stream: &gio::InputStream,
204        cancellable: Option<&gio::Cancellable>,
205    ) -> Result<Document, LoadingError> {
206        // We try to increment the load_limiter to indicate that a new SVG file is being loaded,
207        // possibly in the context of another one that references it.
208        //
209        // The child document takes a clone of this load_limiter at its current value (via
210        // DocumentBuilder::new() below), and also the XML loader takes a clone so that it
211        // can monitor the depth of XInclude invocations.  The child document will then
212        // know that it is at a greater depth than the current one, and so on for
213        // grandchild documents.
214        load_limiter.increment()?;
215
216        let res = xml_load_from_possibly_compressed_stream(
217            session.clone(),
218            DocumentBuilder::new(session, load_options.clone(), load_limiter.clone()),
219            load_options,
220            load_limiter.clone(),
221            stream,
222            cancellable,
223        );
224
225        load_limiter.decrement();
226
227        res
228    }
229
230    /// Utility function to load a document from a static string in tests.
231    #[cfg(test)]
232    pub fn load_from_bytes(input: &'static [u8]) -> Document {
233        let bytes = glib::Bytes::from_static(input);
234        let stream = gio::MemoryInputStream::from_bytes(&bytes);
235
236        let session = Session::new_for_test_suite();
237
238        let document = Document::load_from_stream(
239            session.clone(),
240            Arc::new(LoadOptions::new(UrlResolver::new(None))),
241            LoadingDepthLimiter::new(),
242            &stream.upcast(),
243            None::<&gio::Cancellable>,
244        )
245        .unwrap();
246
247        document.ensure_is_cascaded();
248
249        document
250    }
251
252    /// Gets the root node.  This is guaranteed to be an `<svg>` element.
253    pub fn root(&self) -> Node {
254        self.tree.borrow().clone()
255    }
256
257    /// Looks up a node in this document or one of its resources by its `id` attribute.
258    fn lookup_node(
259        &self,
260        node_id: &NodeId,
261        cancellable: Option<&gio::Cancellable>,
262    ) -> Option<Node> {
263        match node_id {
264            NodeId::Internal(id) => self.lookup_internal_node(id),
265            NodeId::External(url, id) => self
266                .resources
267                .borrow_mut()
268                .lookup_node(&self.session, &self.load_options, url, id, cancellable)
269                .ok(),
270        }
271    }
272
273    /// Looks up a node in this document by its `id` attribute.
274    pub fn lookup_internal_node(&self, id: &str) -> Option<Node> {
275        self.ids.get(id).map(|n| (*n).clone())
276    }
277
278    /// Loads a resource by URL, or returns a pre-loaded one.
279    fn lookup_resource(
280        &self,
281        url: &str,
282        cancellable: Option<&gio::Cancellable>,
283    ) -> Result<Resource, LoadingError> {
284        let aurl = self
285            .load_options
286            .url_resolver
287            .resolve_href(url)
288            .map_err(|_| LoadingError::BadUrl)?;
289
290        self.resources.borrow_mut().lookup_resource(
291            &self.session,
292            &self.load_options,
293            &aurl,
294            cancellable,
295        )
296    }
297
298    /// Runs the CSS cascade on the document tree
299    ///
300    /// This uses the default UserAgent stylesheet, the document's internal stylesheets,
301    /// plus an extra set of stylesheets supplied by the caller.
302    pub fn cascade(&self, extra: &[Stylesheet]) {
303        self.needs_cascade.set(false);
304
305        let stylesheets = {
306            static UA_STYLESHEETS: OnceLock<Vec<Stylesheet>> = OnceLock::new();
307            UA_STYLESHEETS.get_or_init(|| {
308                vec![
309                    Stylesheet::from_data(
310                        include_str!("ua.css"),
311                        &UrlResolver::new(None),
312                        Origin::UserAgent,
313                        self.resources.borrow().load_limiter.clone(),
314                        Session::default(),
315                    )
316                    .expect("could not parse user agent stylesheet for librsvg, there's a bug!"),
317                ]
318            })
319        };
320        css::cascade(
321            &mut self.tree.borrow_mut(),
322            stylesheets,
323            &self.stylesheets,
324            extra,
325            &self.session,
326        );
327    }
328
329    pub fn get_intrinsic_dimensions(&self) -> IntrinsicDimensions {
330        self.ensure_is_cascaded();
331
332        let root = self.root();
333        let cascaded = CascadedValues::new_from_node(&root);
334        let values = cascaded.get();
335        borrow_element_as!(self.root(), Svg).get_intrinsic_dimensions(values)
336    }
337
338    pub fn render_document(
339        &self,
340        cr: &cairo::Context,
341        viewport: &cairo::Rectangle,
342        options: &RenderingOptions,
343    ) -> Result<(), InternalRenderingError> {
344        let root = self.root();
345        self.render_layer(cr, root, viewport, options)
346    }
347
348    pub fn render_layer(
349        &self,
350        cr: &cairo::Context,
351        node: Node,
352        viewport: &cairo::Rectangle,
353        options: &RenderingOptions,
354    ) -> Result<(), InternalRenderingError> {
355        cr.status()?;
356
357        let root = self.root();
358
359        let viewport = Rect::from(*viewport);
360
361        let config = options.to_rendering_configuration(false);
362
363        with_saved_cr(cr, || {
364            self.draw_tree(
365                DrawingMode::LimitToStack { node, root },
366                cr,
367                viewport,
368                config,
369            )
370        })
371        .map(|_bbox| ())
372        .map_err(|err| *err)
373    }
374
375    fn geometry_for_layer(
376        &self,
377        node: Node,
378        viewport: Rect,
379        options: &RenderingOptions,
380    ) -> Result<(Rect, Rect), Box<InternalRenderingError>> {
381        let root = self.root();
382
383        let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
384        let cr = cairo::Context::new(&target)?;
385
386        let config = options.to_rendering_configuration(true);
387
388        let bbox = self.draw_tree(
389            DrawingMode::LimitToStack { node, root },
390            &cr,
391            viewport,
392            config,
393        )?;
394
395        let ink_rect = bbox.ink_rect.unwrap_or_default();
396        let logical_rect = bbox.rect.unwrap_or_default();
397
398        Ok((ink_rect, logical_rect))
399    }
400
401    pub fn get_geometry_for_layer(
402        &self,
403        node: Node,
404        viewport: &cairo::Rectangle,
405        options: &RenderingOptions,
406    ) -> Result<(cairo::Rectangle, cairo::Rectangle), InternalRenderingError> {
407        let viewport = Rect::from(*viewport);
408
409        let (ink_rect, logical_rect) = self
410            .geometry_for_layer(node, viewport, options)
411            .map_err(|err| *err)?;
412
413        Ok((
414            cairo::Rectangle::from(ink_rect),
415            cairo::Rectangle::from(logical_rect),
416        ))
417    }
418
419    fn get_bbox_for_element(
420        &self,
421        node: &Node,
422        options: &RenderingOptions,
423    ) -> Result<BoundingBox, InternalRenderingError> {
424        let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
425        let cr = cairo::Context::new(&target)?;
426
427        let node = node.clone();
428
429        let config = options.to_rendering_configuration(true);
430
431        self.draw_tree(DrawingMode::OnlyNode(node), &cr, unit_rectangle(), config)
432            .map_err(|err| *err)
433    }
434
435    /// Returns (ink_rect, logical_rect)
436    pub fn get_geometry_for_element(
437        &self,
438        node: Node,
439        options: &RenderingOptions,
440    ) -> Result<(cairo::Rectangle, cairo::Rectangle), InternalRenderingError> {
441        let bbox = self.get_bbox_for_element(&node, options)?;
442
443        let ink_rect = bbox.ink_rect.unwrap_or_default();
444        let logical_rect = bbox.rect.unwrap_or_default();
445
446        // Translate so ink_rect is always at offset (0, 0)
447        let ofs = (-ink_rect.x0, -ink_rect.y0);
448
449        Ok((
450            cairo::Rectangle::from(ink_rect.translate(ofs)),
451            cairo::Rectangle::from(logical_rect.translate(ofs)),
452        ))
453    }
454
455    pub fn render_element(
456        &self,
457        cr: &cairo::Context,
458        node: Node,
459        element_viewport: &cairo::Rectangle,
460        options: &RenderingOptions,
461    ) -> Result<(), InternalRenderingError> {
462        cr.status()?;
463
464        let bbox = self.get_bbox_for_element(&node, options)?;
465
466        if bbox.ink_rect.is_none() || bbox.rect.is_none() {
467            // Nothing to draw
468            return Ok(());
469        }
470
471        let ink_r = bbox.ink_rect.unwrap_or_default();
472
473        if ink_r.is_empty() {
474            return Ok(());
475        }
476
477        // Render, transforming so element is at the new viewport's origin
478
479        with_saved_cr(cr, || {
480            let factor = (element_viewport.width() / ink_r.width())
481                .min(element_viewport.height() / ink_r.height());
482
483            cr.translate(element_viewport.x(), element_viewport.y());
484            cr.scale(factor, factor);
485            cr.translate(-ink_r.x0, -ink_r.y0);
486
487            let config = options.to_rendering_configuration(false);
488
489            self.draw_tree(DrawingMode::OnlyNode(node), cr, unit_rectangle(), config)
490        })
491        .map(|_bbox| ())
492        .map_err(|err| *err)
493    }
494
495    /// Wrapper for [`draw_tree`].  This just ensures that the document
496    /// is cascaded before rendering.
497    fn draw_tree(
498        &self,
499        drawing_mode: DrawingMode,
500        cr: &cairo::Context,
501        viewport_rect: Rect,
502        config: RenderingConfiguration,
503    ) -> Result<BoundingBox, Box<InternalRenderingError>> {
504        self.ensure_is_cascaded();
505
506        let cancellable = config.cancellable.clone();
507
508        draw_tree(
509            self.session.clone(),
510            drawing_mode,
511            cr,
512            viewport_rect,
513            config,
514            &mut AcquiredNodes::new(self, cancellable),
515        )
516        .map(|boxed_bbox| *boxed_bbox)
517    }
518
519    fn ensure_is_cascaded(&self) {
520        if self.needs_cascade.get() {
521            self.cascade(&[]);
522        }
523    }
524}
525
526/// Tracks the number of nested file loads within a document
527///
528/// While loading or rendering a document, various operations may
529/// cause extra files to be read.  For example, XML includes, or
530/// `<image src="foo.svg">`, or loading a CSS stylesheet, or CSS
531/// includes.  Librsvg places a limit on the nesting depth, to catch
532/// recursively referenced files.  See the description for
533/// [`limits::MAX_FILE_LOADING_DEPTH`].
534#[derive(Clone)]
535pub struct LoadingDepthLimiter(Cell<usize>);
536
537impl LoadingDepthLimiter {
538    /// Creates a `LoadingDepthLimiter` with a starting depth of 0.
539    pub fn new() -> Self {
540        Self(Cell::new(0))
541    }
542
543    /// Increments the depth of loaded files.
544    ///
545    /// Returns an error if the limit is exceeded; in this case the caller should
546    /// terminate the loading process with an error as quickly as possible.
547    pub fn increment(&self) -> Result<(), LoadingDepthError> {
548        let depth = self.0.get();
549        if depth == limits::MAX_FILE_LOADING_DEPTH {
550            Err(LoadingDepthError)
551        } else {
552            self.0.set(depth + 1);
553            Ok(())
554        }
555    }
556
557    /// Decrements the depth of loaded files.
558    pub fn decrement(&self) {
559        let depth = self.0.get();
560        if depth == 0 {
561            panic!("too many decrements in LoadingDepthLimiter");
562        } else {
563            self.0.set(depth - 1);
564        }
565    }
566}
567
568fn unit_rectangle() -> Rect {
569    Rect::from_size(1.0, 1.0)
570}
571
572/// Any kind of resource loaded while processing an SVG document: images, or SVGs themselves.
573#[derive(Clone)]
574pub enum Resource {
575    Document(Rc<Document>),
576    Image(SharedImageSurface),
577}
578
579/// Set of external resources (other SVG documents, or raster images) referenced by an SVG.
580///
581/// For example, a PNG image in `<image href="foo.png"/>` gets decoded
582/// and stored here, referenced by its URL.
583struct Resources {
584    resources: HashMap<AllowedUrl, Result<Resource, LoadingError>>,
585    load_limiter: LoadingDepthLimiter,
586}
587
588impl Resources {
589    fn new(load_limiter: LoadingDepthLimiter) -> Resources {
590        Resources {
591            resources: Default::default(),
592            load_limiter,
593        }
594    }
595
596    /// Looks up a specific node by its id in another SVG document.
597    ///
598    /// For example, in `<use href="foo.svg#some_node"/>`, or in `filter="url(filters.svg#foo)"`.
599    ///
600    /// The URL is not validated yet; this function will take care of that and return a
601    /// suitable error.
602    fn lookup_node(
603        &mut self,
604        session: &Session,
605        load_options: &LoadOptions,
606        url: &str,
607        id: &str,
608        cancellable: Option<&gio::Cancellable>,
609    ) -> Result<Node, LoadingError> {
610        self.get_extern_document(session, load_options, url, cancellable)
611            .and_then(|resource| match resource {
612                Resource::Document(doc) => doc.lookup_internal_node(id).ok_or(LoadingError::BadUrl),
613                _ => unreachable!("get_extern_document() should already have ensured the document"),
614            })
615    }
616
617    /// Validates the URL and loads an SVG document as a [`Resource`].
618    ///
619    /// The document can then be used whole (`<image href="foo.svg"/>`, or individual
620    /// elements from it can be looked up (`<use href="foo.svg#some_node"/>`).
621    fn get_extern_document(
622        &mut self,
623        session: &Session,
624        load_options: &LoadOptions,
625        href: &str,
626        cancellable: Option<&gio::Cancellable>,
627    ) -> Result<Resource, LoadingError> {
628        let aurl = load_options
629            .url_resolver
630            .resolve_href(href)
631            .map_err(|_| LoadingError::BadUrl)?;
632
633        let resource = self.lookup_resource(session, load_options, &aurl, cancellable)?;
634
635        match resource {
636            Resource::Document(_) => Ok(resource),
637            _ => Err(LoadingError::Other(format!(
638                "{href} is not an SVG document"
639            ))),
640        }
641    }
642
643    /// Loads a resource (an SVG document or a raster image), or returns an already-loaded one.
644    fn lookup_resource(
645        &mut self,
646        session: &Session,
647        load_options: &LoadOptions,
648        aurl: &AllowedUrl,
649        cancellable: Option<&gio::Cancellable>,
650    ) -> Result<Resource, LoadingError> {
651        match self.resources.entry(aurl.clone()) {
652            Entry::Occupied(e) => e.get().clone(),
653
654            Entry::Vacant(e) => {
655                let resource_result = load_resource(
656                    session,
657                    load_options,
658                    self.load_limiter.clone(),
659                    aurl,
660                    cancellable,
661                );
662                e.insert(resource_result.clone());
663                resource_result
664            }
665        }
666    }
667}
668
669#[derive(Clone, Copy)]
670enum ResourceType {
671    Unknown,
672    Svg,
673    Png,
674    Jpeg,
675    Gif,
676    WebP,
677    Avif,
678}
679
680// Compares the fields of `Mime`, but ignores its `parameters`
681fn is_mime_type(mime: &Mime, type_: &str, subtype: &str) -> bool {
682    mime.type_ == type_ && mime.subtype == subtype
683}
684
685impl ResourceType {
686    fn from(mime_type: &Option<Mime>) -> ResourceType {
687        match mime_type {
688            None => ResourceType::Unknown,
689
690            // See issue #548 - data: URLs without a MIME-type automatically
691            // fall back to "text/plain;charset=US-ASCII".  Some (old?) versions of
692            // Adobe Illustrator generate data: URLs without MIME-type for image
693            // data.  We'll catch this and fall back to sniffing by unsetting the
694            // content_type.
695            Some(x) if *x == Mime::from_str("text/plain;charset=US-ASCII").unwrap() => {
696                ResourceType::Unknown
697            }
698
699            Some(x) if is_mime_type(x, "image", "svg+xml") => ResourceType::Svg,
700            Some(x) if is_mime_type(x, "image", "png") => ResourceType::Png,
701            Some(x) if is_mime_type(x, "image", "jpeg") => ResourceType::Jpeg,
702            Some(x) if is_mime_type(x, "image", "gif") => ResourceType::Gif,
703            Some(x) if is_mime_type(x, "image", "webp") => ResourceType::WebP,
704            Some(x) if is_mime_type(x, "image", "avif") => ResourceType::Avif,
705
706            _ => ResourceType::Unknown,
707        }
708    }
709
710    fn is_known(&self) -> bool {
711        !matches!(*self, ResourceType::Unknown)
712    }
713
714    fn to_image_format(self) -> image::ImageFormat {
715        use ResourceType::*;
716
717        match self {
718            Svg => unreachable!(),
719
720            Png => image::ImageFormat::Png,
721            Jpeg => image::ImageFormat::Jpeg,
722            Gif => image::ImageFormat::Gif,
723            WebP => image::ImageFormat::WebP,
724            Avif => image::ImageFormat::Avif,
725
726            _ => unreachable!(),
727        }
728    }
729}
730
731/// Loads the entire contents of a URL, sniffs them, and decodes them as a [`Resource`]
732/// for an SVG or raster image.
733///
734/// Assumes that `gio`'s content-sniffing machinery is working correctly.  Anything that
735/// doesn't sniff like an SVG document will be decoded as a raster image.
736///
737/// This handles `data:` URLs correctly, by decoding them into binary data, and then
738/// sniffing it or using the declared MIME type in the `data:` URL itself.
739fn load_resource(
740    session: &Session,
741    load_options: &LoadOptions,
742    load_limiter: LoadingDepthLimiter,
743    aurl: &AllowedUrl,
744    cancellable: Option<&gio::Cancellable>,
745) -> Result<Resource, LoadingError> {
746    let data = io::acquire_data(aurl, cancellable)?;
747
748    let BinaryData {
749        data: bytes,
750        mime_type,
751    } = data;
752
753    let resource_type = ResourceType::from(&mime_type);
754
755    if resource_type.is_known() {
756        use ResourceType::*;
757
758        match resource_type {
759            Png | Jpeg | Gif | WebP | Avif => {
760                let format = resource_type.to_image_format();
761                load_image_resource_from_bytes(load_options, aurl, bytes, format)
762            }
763            Svg => load_svg_resource_from_bytes(
764                session,
765                load_options,
766                load_limiter,
767                aurl,
768                bytes,
769                cancellable,
770            ),
771            _ => unreachable!(),
772        }
773    } else {
774        // We don't know the MIME type of the data.  Sniff it, hand off raster images to the image crate,
775        // or else fall back to trying to load as an SVG.
776
777        if let Ok(format) = image::guess_format(&bytes) {
778            load_image_resource_from_bytes(load_options, aurl, bytes, format)
779        } else {
780            load_svg_resource_from_bytes(
781                session,
782                load_options,
783                load_limiter,
784                aurl,
785                bytes,
786                cancellable,
787            )
788        }
789    }
790}
791
792/// Parses [`BinaryData`] that is known to be an SVG document, using librsvg itself.
793fn load_svg_resource_from_bytes(
794    session: &Session,
795    load_options: &LoadOptions,
796    load_limiter: LoadingDepthLimiter,
797    aurl: &AllowedUrl,
798    input_bytes: Vec<u8>,
799    cancellable: Option<&gio::Cancellable>,
800) -> Result<Resource, LoadingError> {
801    let bytes = glib::Bytes::from_owned(input_bytes);
802    let stream = gio::MemoryInputStream::from_bytes(&bytes);
803
804    let document = Document::load_from_stream(
805        session.clone(),
806        Arc::new(load_options.copy_with_base_url(aurl)),
807        load_limiter,
808        &stream.upcast(),
809        cancellable,
810    )?;
811
812    document.ensure_is_cascaded();
813
814    Ok(Resource::Document(Rc::new(document)))
815}
816
817/// Decodes [`BinaryData`] that is presumed to be a raster image.
818///
819/// To know which decoder to use (or to even decide if this is a supported image format),
820/// this function uses the `mime_type` field in the [`BinaryData`].
821///
822/// The [`AllowedUrl`] is not used for decoding; it is just to construct an error message
823/// for the return value.
824fn load_image_resource_from_bytes(
825    load_options: &LoadOptions,
826    aurl: &AllowedUrl,
827    bytes: Vec<u8>,
828    format: image::ImageFormat,
829) -> Result<Resource, LoadingError> {
830    if bytes.is_empty() {
831        return Err(LoadingError::Other(String::from("no image data")));
832    }
833
834    // The image crate is fuzzed separately by its own OSS-Fuzz project.  When librsvg
835    // itself is being fuzzed (`cfg(fuzzing)` is set by `cargo fuzz`), don't decode images,
836    // so the fuzzer spends its time on librsvg's code instead of on the decoders.
837    if cfg!(fuzzing) {
838        return Err(LoadingError::Other(String::from(
839            "images are not decoded while fuzzing",
840        )));
841    }
842
843    load_image_with_image_rs(aurl, bytes, format, load_options)
844}
845
846/// Decides whether the specified MIME type is supported as a raster image format.
847///
848/// Librsvg explicitly only supports PNG/JPEG/GIF/WEBP, and AVIF optionally.  See the
849/// documentation on [supported raster image formats][formats] for details.
850///
851/// [formats]: https://gnome.pages.gitlab.gnome.org/librsvg/devel-docs/features.html#supported-raster-image-formats
852fn is_supported_image_format(format: image::ImageFormat) -> bool {
853    use image::ImageFormat::*;
854
855    match format {
856        Png => true,
857        Jpeg => true,
858        Gif => true,
859        WebP => true,
860
861        #[cfg(feature = "avif")]
862        Avif => true,
863
864        _ => false,
865    }
866}
867
868fn image_format_to_content_type(format: image::ImageFormat) -> String {
869    use image::ImageFormat::*;
870
871    let mime_type = match format {
872        Png => "image/png",
873        Jpeg => "image/jpeg",
874        Gif => "image/gif",
875        WebP => "image/webp",
876        Avif => "image/avif",
877        _ => unreachable!("we should have already filtered supported image types"),
878    };
879
880    mime_type.to_string()
881}
882
883fn load_image_with_image_rs(
884    aurl: &AllowedUrl,
885    bytes: Vec<u8>,
886    format: image::ImageFormat,
887    load_options: &LoadOptions,
888) -> Result<Resource, LoadingError> {
889    if is_supported_image_format(format) {
890        let cursor = Cursor::new(&bytes);
891
892        let mut reader = image::ImageReader::with_format(cursor, format);
893        if load_options.unlimited_size {
894            reader.no_limits();
895        }
896
897        let image = reader
898            .decode()
899            .map_err(|e| LoadingError::Other(format!("error decoding image: {e}")))?;
900
901        let bytes = if load_options.keep_image_data {
902            Some(bytes)
903        } else {
904            None
905        };
906
907        let content_type = image_format_to_content_type(format);
908
909        let surface = SharedImageSurface::from_image(&image, Some(&content_type), bytes)
910            .map_err(|e| image_loading_error_from_cairo(e, aurl))?;
911
912        Ok(Resource::Image(surface))
913    } else {
914        Err(LoadingError::Other(String::from(
915            "unsupported image format {format:?}",
916        )))
917    }
918}
919
920/// Formats a URL for human consumption, as in error messages.  This is to
921/// reduce very long `data:` URLs to an abbreviated version.
922fn human_readable_url(aurl: &AllowedUrl) -> &str {
923    if aurl.scheme() == "data" {
924        // avoid printing a huge data: URL for image data
925        "data URL"
926    } else {
927        aurl.as_ref()
928    }
929}
930
931/// Converts a `cairo::Error` that happened while wrapping a decoded raster image
932/// into a `LoadingError` augmented with the image's URL.
933fn image_loading_error_from_cairo(status: cairo::Error, aurl: &AllowedUrl) -> LoadingError {
934    let url = human_readable_url(aurl);
935
936    match status {
937        cairo::Error::NoMemory => LoadingError::OutOfMemory(format!("loading image: {url}")),
938        cairo::Error::InvalidSize => LoadingError::Other(format!("image too big: {url}")),
939        _ => LoadingError::Other(format!("cairo error: {status}")),
940    }
941}
942
943pub struct AcquiredNode {
944    stack: Option<Rc<RefCell<NodeStack>>>,
945    node: Node,
946}
947
948impl Drop for AcquiredNode {
949    fn drop(&mut self) {
950        if let Some(ref stack) = self.stack {
951            let mut stack = stack.borrow_mut();
952            let last = stack.pop().unwrap();
953            assert!(last == self.node);
954        }
955    }
956}
957
958impl AcquiredNode {
959    pub fn get(&self) -> &Node {
960        &self.node
961    }
962}
963
964/// Detects circular references between nodes, and enforces referencing limits.
965///
966/// Consider this fragment of SVG:
967///
968/// ```xml
969/// <pattern id="foo">
970///   <rect width="1" height="1" fill="url(#foo)"/>
971/// </pattern>
972/// ```
973///
974/// The pattern has a child element that references the pattern itself.  This kind of circular
975/// reference is invalid.  The `AcquiredNodes` struct is passed around
976/// wherever it may be necessary to resolve references to nodes, or to access nodes
977/// "elsewhere" in the DOM that is not the current subtree.
978///
979/// Also, such constructs that reference other elements can be maliciously arranged like
980/// in the [billion laughs attack][lol], to cause huge amounts of CPU to be consumed through
981/// creating an exponential number of references.  `AcquiredNodes` imposes a hard limit on
982/// the number of references that can be resolved for typical, well-behaved SVG documents.
983///
984/// The [`Self::acquire()`] and [`Self::acquire_ref()`] methods return an [`AcquiredNode`], which
985/// acts like a smart pointer for a [`Node`].  Once a node has been acquired, it cannot be
986/// acquired again until its [`AcquiredNode`] is dropped.  In the example above, a graphic element
987/// would acquire the `pattern`, which would then acquire its `rect` child, which then would fail
988/// to re-acquired the `pattern` — thus signaling a circular reference.
989///
990/// Those methods return an [`AcquireError`] to signal circular references.  Also, they
991/// can return [`AcquireError::MaxReferencesExceeded`] if the aforementioned referencing limit
992/// is exceeded.
993///
994/// [lol]: https://bitbucket.org/tiran/defusedxml
995pub struct AcquiredNodes<'i> {
996    document: &'i Document,
997    num_elements_acquired: usize,
998    node_stack: Rc<RefCell<NodeStack>>,
999    nodes_with_cycles: Vec<Node>,
1000    cancellable: Option<gio::Cancellable>,
1001}
1002
1003impl<'i> AcquiredNodes<'i> {
1004    pub fn new(document: &Document, cancellable: Option<gio::Cancellable>) -> AcquiredNodes<'_> {
1005        AcquiredNodes {
1006            document,
1007            num_elements_acquired: 0,
1008            node_stack: Rc::new(RefCell::new(NodeStack::new())),
1009            nodes_with_cycles: Vec::new(),
1010            cancellable,
1011        }
1012    }
1013
1014    pub fn lookup_resource(&self, url: &str) -> Result<Resource, LoadingError> {
1015        self.document
1016            .lookup_resource(url, self.cancellable.as_ref())
1017    }
1018
1019    /// Acquires a node by its id.
1020    ///
1021    /// This is typically used during an "early resolution" stage, when XML `id`s are being
1022    /// resolved to node references.
1023    ///
1024    /// The `referencing_element_name` is just used for logging error messages, to identify
1025    /// where the `node_id` is being referenced from.
1026    pub fn acquire(
1027        &mut self,
1028        referencing_element_name: &str,
1029        node_id: &NodeId,
1030    ) -> Result<AcquiredNode, AcquireError> {
1031        let session = &self.document.session;
1032
1033        self.num_elements_acquired += 1;
1034
1035        // This is a mitigation for SVG files that try to instance a huge number of
1036        // elements via <use>, recursive patterns, etc.  See limits.rs for details.
1037        if self.num_elements_acquired > limits::MAX_REFERENCED_ELEMENTS {
1038            rsvg_log!(
1039                session,
1040                "maximum number of referenced elements exceeded in {referencing_element_name}"
1041            );
1042            return Err(AcquireError::MaxReferencesExceeded);
1043        }
1044
1045        // FIXME: callers shouldn't have to know that get_node() can initiate a file load.
1046        // Maybe we should have the following stages:
1047        //   - load main SVG XML
1048        //
1049        //   - load secondary resources: SVG XML and other files like images
1050        //
1051        //   - Now that all files are loaded, resolve URL references
1052        let node = self
1053            .document
1054            .lookup_node(node_id, self.cancellable.as_ref())
1055            .ok_or_else(|| {
1056                rsvg_log!(
1057                    session,
1058                    "reference \"{}\" does not exist, referenced from {referencing_element_name}",
1059                    node_id
1060                );
1061
1062                AcquireError::LinkNotFound(node_id.clone())
1063            })?;
1064
1065        if self.nodes_with_cycles.contains(&node) {
1066            rsvg_log!(
1067                session,
1068                "circular reference from {referencing_element_name} to element {node}",
1069            );
1070            return Err(AcquireError::CircularReference(node.clone()));
1071        }
1072
1073        if node.borrow_element().is_accessed_by_reference() {
1074            self.acquire_ref(&node)
1075        } else {
1076            Ok(AcquiredNode { stack: None, node })
1077        }
1078    }
1079
1080    /// Acquires a node whose reference is already known.
1081    ///
1082    /// This is useful for cases where a node is initially referenced by its id with
1083    /// [`Self::acquire`] and kept around for later use.  During the later use, the node
1084    /// needs to be re-acquired with this method.  For example:
1085    ///
1086    /// * At an "early resolution" stage, `acquire()` a pattern by its id, and keep around its
1087    ///   [`Node`] reference.
1088    ///
1089    /// * At the drawing stage, `acquire_ref()` the pattern node that we already had, so that
1090    ///   its child elements that reference other paint servers will be able to detect circular
1091    ///   references to the pattern.
1092    pub fn acquire_ref(&mut self, node: &Node) -> Result<AcquiredNode, AcquireError> {
1093        let session = &self.document.session;
1094
1095        if self.nodes_with_cycles.contains(node) {
1096            rsvg_log!(session, "circular reference for {node}");
1097            Err(AcquireError::CircularReference(node.clone()))
1098        } else if self.node_stack.borrow().contains(node) {
1099            self.nodes_with_cycles.push(node.clone());
1100            rsvg_log!(session, "circular reference for {node}");
1101            Err(AcquireError::CircularReference(node.clone()))
1102        } else {
1103            self.node_stack.borrow_mut().push(node);
1104            Ok(AcquiredNode {
1105                stack: Some(self.node_stack.clone()),
1106                node: node.clone(),
1107            })
1108        }
1109    }
1110}
1111
1112/// Keeps a stack of nodes and can check if a certain node is contained in the stack
1113///
1114/// Sometimes parts of the code cannot plainly use the implicit stack of acquired
1115/// nodes as maintained by DrawingCtx::acquire_node(), and they must keep their
1116/// own stack of nodes to test for reference cycles.  NodeStack can be used to do that.
1117pub struct NodeStack(Vec<Node>);
1118
1119impl NodeStack {
1120    pub fn new() -> NodeStack {
1121        NodeStack(Vec::new())
1122    }
1123
1124    pub fn push(&mut self, node: &Node) {
1125        self.0.push(node.clone());
1126    }
1127
1128    pub fn pop(&mut self) -> Option<Node> {
1129        self.0.pop()
1130    }
1131
1132    pub fn contains(&self, node: &Node) -> bool {
1133        self.0.contains(node)
1134    }
1135}
1136
1137/// Used to build a tree of SVG nodes while an XML document is being read.
1138///
1139/// This struct holds the document-related state while loading an SVG document from XML:
1140/// the loading options, the partially-built tree of nodes, the CSS stylesheets that
1141/// appear while loading the document.
1142///
1143/// The XML loader asks a `DocumentBuilder` to
1144/// [`append_element`][DocumentBuilder::append_element],
1145/// [`append_characters`][DocumentBuilder::append_characters], etc.  When all the XML has
1146/// been consumed, the caller can use [`build`][DocumentBuilder::build] to get a
1147/// fully-loaded [`Document`].
1148pub struct DocumentBuilder {
1149    /// Metadata for the document's lifetime.
1150    session: Session,
1151
1152    /// Loading options; mainly the URL resolver.
1153    load_options: Arc<LoadOptions>,
1154
1155    /// To limit the depth of referenced files
1156    load_limiter: LoadingDepthLimiter,
1157
1158    /// Root node of the tree.
1159    tree: Option<Node>,
1160
1161    /// Mapping from `id` attributes to nodes.
1162    ids: HashMap<String, Node>,
1163
1164    /// Stylesheets defined in the document.
1165    stylesheets: Vec<Stylesheet>,
1166}
1167
1168impl DocumentBuilder {
1169    pub fn new(
1170        session: Session,
1171        load_options: Arc<LoadOptions>,
1172        load_limiter: LoadingDepthLimiter,
1173    ) -> DocumentBuilder {
1174        DocumentBuilder {
1175            session,
1176            load_options,
1177            load_limiter,
1178            tree: None,
1179            ids: HashMap::new(),
1180            stylesheets: Vec::new(),
1181        }
1182    }
1183
1184    /// Adds a stylesheet in order to the document.
1185    ///
1186    /// Stylesheets will later be matched in the order in which they were added.
1187    pub fn append_stylesheet(&mut self, stylesheet: Stylesheet) {
1188        self.stylesheets.push(stylesheet);
1189    }
1190
1191    /// Creates an element of the specified `name` as a child of `parent`.
1192    ///
1193    /// This is the main function to create new SVG elements while parsing XML.
1194    ///
1195    /// `name` is the XML element's name, for example `rect`.
1196    ///
1197    /// `attrs` has the XML element's attributes, e.g. cx/cy/r for `<circle cx="0" cy="0"
1198    /// r="5">`.
1199    ///
1200    /// If `parent` is `None` it means that we are creating the root node in the tree of
1201    /// elements.  The code will later validate that this is indeed an `<svg>` element.
1202    pub fn append_element(
1203        &mut self,
1204        name: &QualName,
1205        attrs: Attributes,
1206        parent: Option<Node>,
1207    ) -> Node {
1208        let node = Node::new(NodeData::new_element(&self.session, name, attrs));
1209
1210        if let Some(id) = node.borrow_element().get_id() {
1211            match self.ids.entry(id.to_string()) {
1212                Entry::Occupied(_) => {
1213                    rsvg_log!(self.session, "ignoring duplicate id {id} for {node}");
1214                }
1215
1216                Entry::Vacant(e) => {
1217                    e.insert(node.clone());
1218                }
1219            }
1220        }
1221
1222        if let Some(parent) = parent {
1223            parent.append(node.clone());
1224        } else if self.tree.is_none() {
1225            self.tree = Some(node.clone());
1226        } else {
1227            panic!("The tree root has already been set");
1228        }
1229
1230        node
1231    }
1232
1233    /// Creates a node for an XML text element as a child of `parent`.
1234    pub fn append_characters(&mut self, text: &str, parent: &mut Node) {
1235        if !text.is_empty() {
1236            // When the last child is a Chars node we can coalesce
1237            // the text and avoid screwing up the Pango layouts
1238            if let Some(child) = parent.last_child().filter(|c| c.is_chars()) {
1239                child.borrow_chars().append(text);
1240            } else {
1241                parent.append(Node::new(NodeData::new_chars(text)));
1242            };
1243        }
1244    }
1245
1246    /// Does the final validation on the `Document` being read, and returns it.
1247    pub fn build(self) -> Result<Document, LoadingError> {
1248        let DocumentBuilder {
1249            load_options,
1250            load_limiter,
1251            session,
1252            tree,
1253            ids,
1254            stylesheets,
1255            ..
1256        } = self;
1257
1258        match tree {
1259            Some(root) if root.is_element() => {
1260                if is_element_of_type!(root, Svg) {
1261                    let document = Document {
1262                        tree: RefCell::new(root),
1263                        session: session.clone(),
1264                        ids,
1265                        resources: RefCell::new(Resources::new(load_limiter)),
1266                        load_options,
1267                        stylesheets,
1268                        needs_cascade: Cell::new(true),
1269                    };
1270
1271                    Ok(document)
1272                } else {
1273                    Err(LoadingError::NoSvgRoot)
1274                }
1275            }
1276            _ => Err(LoadingError::NoSvgRoot),
1277        }
1278    }
1279}
1280
1281#[cfg(test)]
1282mod tests {
1283    use super::*;
1284
1285    #[test]
1286    fn parses_node_id() {
1287        assert_eq!(
1288            NodeId::parse("#foo").unwrap(),
1289            NodeId::Internal("foo".to_string())
1290        );
1291
1292        assert_eq!(
1293            NodeId::parse("uri#foo").unwrap(),
1294            NodeId::External("uri".to_string(), "foo".to_string())
1295        );
1296
1297        assert!(matches!(
1298            NodeId::parse("uri"),
1299            Err(NodeIdError::NodeIdRequired)
1300        ));
1301    }
1302
1303    #[test]
1304    fn ignores_stylesheet_with_invalid_utf8() {
1305        let handle = crate::api::Loader::new()
1306            .read_path("tests/fixtures/loading/non-utf8-stylesheet.svg")
1307            .unwrap();
1308        assert!(handle.document.stylesheets.is_empty());
1309    }
1310}