1
//! Main SVG document structure.
2

            
3
use data_url::mime::Mime;
4
use glib::prelude::*;
5
use markup5ever::QualName;
6
use std::cell::Cell;
7
use std::collections::hash_map::Entry;
8
use std::collections::HashMap;
9
use std::fmt;
10
use std::include_str;
11
use std::io::Cursor;
12
use std::rc::Rc;
13
use std::str::FromStr;
14
use std::sync::Arc;
15
use std::{cell::RefCell, sync::OnceLock};
16

            
17
use crate::accept_language::UserLanguage;
18
use crate::bbox::BoundingBox;
19
use crate::borrow_element_as;
20
use crate::css::{self, Origin, Stylesheet};
21
use crate::dpi::Dpi;
22
use crate::drawing_ctx::{
23
    draw_tree, with_saved_cr, DrawingMode, RenderingConfiguration, SvgNesting,
24
};
25
use crate::error::{AcquireError, InternalRenderingError, LoadingError, NodeIdError};
26
use crate::io::{self, BinaryData};
27
use crate::is_element_of_type;
28
use crate::limits;
29
use crate::node::{CascadedValues, Node, NodeBorrow, NodeData};
30
use crate::rect::Rect;
31
use crate::rsvg_log;
32
use crate::session::Session;
33
use crate::structure::IntrinsicDimensions;
34
use crate::surface_utils::shared_surface::SharedImageSurface;
35
use crate::url_resolver::{AllowedUrl, UrlResolver};
36
use crate::xml::{xml_load_from_possibly_compressed_stream, Attributes};
37

            
38
/// Identifier of a node
39
#[derive(Debug, PartialEq, Clone)]
40
pub enum NodeId {
41
    /// element id
42
    Internal(String),
43
    /// url, element id
44
    External(String, String),
45
}
46

            
47
impl NodeId {
48
47920
    pub fn parse(href: &str) -> Result<NodeId, NodeIdError> {
49
47920
        let (url, id) = match href.rfind('#') {
50
895
            None => (Some(href), None),
51
45803
            Some(0) => (None, Some(&href[1..])),
52
1222
            Some(p) => (Some(&href[..p]), Some(&href[(p + 1)..])),
53
        };
54

            
55
47920
        match (url, id) {
56
45803
            (None, Some(id)) if !id.is_empty() => Ok(NodeId::Internal(String::from(id))),
57
1222
            (Some(url), Some(id)) if !id.is_empty() => {
58
1222
                Ok(NodeId::External(String::from(url), String::from(id)))
59
            }
60
895
            _ => Err(NodeIdError::NodeIdRequired),
61
        }
62
47920
    }
63
}
64

            
65
impl fmt::Display for NodeId {
66
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67
        match self {
68
            NodeId::Internal(id) => write!(f, "#{id}"),
69
            NodeId::External(url, id) => write!(f, "{url}#{id}"),
70
        }
71
    }
72
}
73

            
74
/// Loading options for SVG documents.
75
pub struct LoadOptions {
76
    /// Load url resolver; all references will be resolved with respect to this.
77
    pub url_resolver: UrlResolver,
78

            
79
    /// Whether to turn off size limits in libxml2.
80
    pub unlimited_size: bool,
81

            
82
    /// Whether to keep original (undecoded) image data to embed in Cairo PDF surfaces.
83
    pub keep_image_data: bool,
84
}
85

            
86
impl LoadOptions {
87
    /// Creates a `LoadOptions` with defaults, and sets the `url resolver`.
88
20170
    pub fn new(url_resolver: UrlResolver) -> Self {
89
20170
        LoadOptions {
90
20170
            url_resolver,
91
20170
            unlimited_size: false,
92
20170
            keep_image_data: false,
93
20170
        }
94
20170
    }
95

            
96
    /// Sets whether libxml2's limits on memory usage should be turned off.
97
    ///
98
    /// This should only be done for trusted data.
99
20159
    pub fn with_unlimited_size(mut self, unlimited: bool) -> Self {
100
20159
        self.unlimited_size = unlimited;
101
20159
        self
102
20159
    }
103

            
104
    /// Sets whether to keep the original compressed image data from referenced JPEG/PNG images.
105
    ///
106
    /// This is only useful for rendering to Cairo PDF
107
    /// surfaces, which can embed the original, compressed image data instead of uncompressed
108
    /// RGB buffers.
109
20159
    pub fn keep_image_data(mut self, keep: bool) -> Self {
110
20159
        self.keep_image_data = keep;
111
20159
        self
112
20159
    }
113

            
114
    /// Creates a new `LoadOptions` with a different `url resolver`.
115
    ///
116
    /// This is used when loading a referenced file that may in turn cause other files
117
    /// to be loaded, for example `<image xlink:href="subimage.svg"/>`
118
114
    pub fn copy_with_base_url(&self, base_url: &AllowedUrl) -> Self {
119
114
        let mut url_resolver = self.url_resolver.clone();
120
114
        url_resolver.base_url = Some((**base_url).clone());
121
114

            
122
114
        LoadOptions {
123
114
            url_resolver,
124
114
            unlimited_size: self.unlimited_size,
125
114
            keep_image_data: self.keep_image_data,
126
114
        }
127
114
    }
128
}
129

            
130
/// Document-level rendering options.
131
///
132
/// This gets then converted to a [`drawing_ctx::RenderingConfiguration`] when all the
133
/// parameters are known.
134
pub struct RenderingOptions {
135
    pub dpi: Dpi,
136
    pub cancellable: Option<gio::Cancellable>,
137
    pub user_language: UserLanguage,
138
    pub svg_nesting: SvgNesting,
139
    pub testing: bool,
140
}
141

            
142
impl RenderingOptions {
143
    /// Copies the options to a [`RenderingConfiguration`], and adds the `measuring` flag.
144
20653
    fn to_rendering_configuration(&self, measuring: bool) -> RenderingConfiguration {
145
20653
        RenderingConfiguration {
146
20653
            dpi: self.dpi,
147
20653
            cancellable: self.cancellable.clone(),
148
20653
            user_language: self.user_language.clone(),
149
20653
            svg_nesting: self.svg_nesting,
150
20653
            testing: self.testing,
151
20653
            measuring,
152
20653
        }
153
20653
    }
154
}
155

            
156
/// A loaded SVG file and its derived data.
157
pub struct Document {
158
    /// Tree of nodes; the root is guaranteed to be an `<svg>` element.
159
    ///
160
    /// This is inside a [`RefCell`] because when cascading lazily, we
161
    /// need a mutable tree.
162
    tree: RefCell<Node>,
163

            
164
    /// Metadata about the SVG handle.
165
    session: Session,
166

            
167
    /// Mapping from `id` attributes to nodes.
168
    ids: HashMap<String, Node>,
169

            
170
    /// Othewr SVG documents and images referenced from this document.
171
    ///
172
    /// This requires requires interior mutability because we load resources all over the
173
    /// place.  Eventually we'll be able to do this once, at loading time, and keep this
174
    /// immutable.
175
    resources: RefCell<Resources>,
176

            
177
    /// Used to load referenced resources.
178
    load_options: Arc<LoadOptions>,
179

            
180
    /// Stylesheets defined in the document.
181
    stylesheets: Vec<Stylesheet>,
182

            
183
    /// Whether there's a pending cascade operation.
184
    ///
185
    /// The document starts un-cascaded and with this flag turned on,
186
    /// to avoid a double cascade if
187
    /// [`crate::SvgHandle::set_stylesheet`] is called after loading
188
    /// the document.
189
    needs_cascade: Cell<bool>,
190
}
191

            
192
impl Document {
193
    /// Constructs a `Document` by loading it from a stream.
194
    ///
195
    /// Note that the document is **not** cascaded just after loading.  Cascading is done lazily;
196
    /// call [`Document::ensure_is_cascaded`] if you need a cascaded tree of elements.
197
20284
    pub fn load_from_stream(
198
20284
        session: Session,
199
20284
        load_options: Arc<LoadOptions>,
200
20284
        stream: &gio::InputStream,
201
20284
        cancellable: Option<&gio::Cancellable>,
202
20284
    ) -> Result<Document, LoadingError> {
203
20284
        xml_load_from_possibly_compressed_stream(
204
20284
            session.clone(),
205
20284
            DocumentBuilder::new(session, load_options.clone()),
206
20284
            load_options,
207
20284
            stream,
208
20284
            cancellable,
209
20284
        )
210
20284
    }
211

            
212
    /// Utility function to load a document from a static string in tests.
213
    #[cfg(test)]
214
11
    pub fn load_from_bytes(input: &'static [u8]) -> Document {
215
11
        let bytes = glib::Bytes::from_static(input);
216
11
        let stream = gio::MemoryInputStream::from_bytes(&bytes);
217
11

            
218
11
        let session = Session::new_for_test_suite();
219
11

            
220
11
        let document = Document::load_from_stream(
221
11
            session.clone(),
222
11
            Arc::new(LoadOptions::new(UrlResolver::new(None))),
223
11
            &stream.upcast(),
224
11
            None::<&gio::Cancellable>,
225
11
        )
226
11
        .unwrap();
227
11

            
228
11
        document.ensure_is_cascaded();
229
11

            
230
11
        document
231
11
    }
232

            
233
    /// Gets the root node.  This is guaranteed to be an `<svg>` element.
234
82669
    pub fn root(&self) -> Node {
235
82669
        self.tree.borrow().clone()
236
82669
    }
237

            
238
    /// Looks up a node in this document or one of its resources by its `id` attribute.
239
19023183
    fn lookup_node(
240
19023183
        &self,
241
19023183
        node_id: &NodeId,
242
19023183
        cancellable: Option<&gio::Cancellable>,
243
19023183
    ) -> Option<Node> {
244
19023183
        match node_id {
245
19019896
            NodeId::Internal(id) => self.lookup_internal_node(id),
246
3287
            NodeId::External(url, id) => self
247
3287
                .resources
248
3287
                .borrow_mut()
249
3287
                .lookup_node(&self.session, &self.load_options, url, id, cancellable)
250
3287
                .ok(),
251
        }
252
19023183
    }
253

            
254
    /// Looks up a node in this document by its `id` attribute.
255
19021568
    pub fn lookup_internal_node(&self, id: &str) -> Option<Node> {
256
19021568
        self.ids.get(id).map(|n| (*n).clone())
257
19021568
    }
258

            
259
    /// Loads a resource by URL, or returns a pre-loaded one.
260
2888
    fn lookup_resource(
261
2888
        &self,
262
2888
        url: &str,
263
2888
        cancellable: Option<&gio::Cancellable>,
264
2888
    ) -> Result<Resource, LoadingError> {
265
2888
        let aurl = self
266
2888
            .load_options
267
2888
            .url_resolver
268
2888
            .resolve_href(url)
269
2888
            .map_err(|_| LoadingError::BadUrl)?;
270

            
271
2869
        self.resources.borrow_mut().lookup_resource(
272
2869
            &self.session,
273
2869
            &self.load_options,
274
2869
            &aurl,
275
2869
            cancellable,
276
2869
        )
277
2888
    }
278

            
279
    /// Runs the CSS cascade on the document tree
280
    ///
281
    /// This uses the default UserAgent stylesheet, the document's internal stylesheets,
282
    /// plus an extra set of stylesheets supplied by the caller.
283
19562
    pub fn cascade(&self, extra: &[Stylesheet]) {
284
19562
        self.needs_cascade.set(false);
285
19562

            
286
19562
        let stylesheets = {
287
            static UA_STYLESHEETS: OnceLock<Vec<Stylesheet>> = OnceLock::new();
288
19562
            UA_STYLESHEETS.get_or_init(|| {
289
1920
                vec![Stylesheet::from_data(
290
1920
                    include_str!("ua.css"),
291
1920
                    &UrlResolver::new(None),
292
1920
                    Origin::UserAgent,
293
1920
                    Session::default(),
294
1920
                )
295
1920
                .expect("could not parse user agent stylesheet for librsvg, there's a bug!")]
296
19562
            })
297
19562
        };
298
19562
        css::cascade(
299
19562
            &mut self.tree.borrow_mut(),
300
19562
            stylesheets,
301
19562
            &self.stylesheets,
302
19562
            extra,
303
19562
            &self.session,
304
19562
        );
305
19562
    }
306

            
307
20387
    pub fn get_intrinsic_dimensions(&self) -> IntrinsicDimensions {
308
20387
        self.ensure_is_cascaded();
309
20387

            
310
20387
        let root = self.root();
311
20387
        let cascaded = CascadedValues::new_from_node(&root);
312
20387
        let values = cascaded.get();
313
20387
        borrow_element_as!(self.root(), Svg).get_intrinsic_dimensions(values)
314
20387
    }
315

            
316
18981
    pub fn render_document(
317
18981
        &self,
318
18981
        cr: &cairo::Context,
319
18981
        viewport: &cairo::Rectangle,
320
18981
        options: &RenderingOptions,
321
18981
    ) -> Result<(), InternalRenderingError> {
322
18981
        let root = self.root();
323
18981
        self.render_layer(cr, root, viewport, options)
324
18981
    }
325

            
326
19000
    pub fn render_layer(
327
19000
        &self,
328
19000
        cr: &cairo::Context,
329
19000
        node: Node,
330
19000
        viewport: &cairo::Rectangle,
331
19000
        options: &RenderingOptions,
332
19000
    ) -> Result<(), InternalRenderingError> {
333
19000
        cr.status()?;
334

            
335
19000
        let root = self.root();
336
19000

            
337
19000
        let viewport = Rect::from(*viewport);
338
19000

            
339
19000
        let config = options.to_rendering_configuration(false);
340
19000

            
341
19000
        with_saved_cr(cr, || {
342
19000
            self.draw_tree(
343
19000
                DrawingMode::LimitToStack { node, root },
344
19000
                cr,
345
19000
                viewport,
346
19000
                config,
347
19000
            )
348
19000
            .map(|_bbox| ())
349
19000
        })
350
19000
    }
351

            
352
1368
    fn geometry_for_layer(
353
1368
        &self,
354
1368
        node: Node,
355
1368
        viewport: Rect,
356
1368
        options: &RenderingOptions,
357
1368
    ) -> Result<(Rect, Rect), InternalRenderingError> {
358
1368
        let root = self.root();
359

            
360
1368
        let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
361
1368
        let cr = cairo::Context::new(&target)?;
362

            
363
1368
        let config = options.to_rendering_configuration(true);
364

            
365
1368
        let bbox = self.draw_tree(
366
1368
            DrawingMode::LimitToStack { node, root },
367
1368
            &cr,
368
1368
            viewport,
369
1368
            config,
370
1368
        )?;
371

            
372
1368
        let ink_rect = bbox.ink_rect.unwrap_or_default();
373
1368
        let logical_rect = bbox.rect.unwrap_or_default();
374
1368

            
375
1368
        Ok((ink_rect, logical_rect))
376
1368
    }
377

            
378
1368
    pub fn get_geometry_for_layer(
379
1368
        &self,
380
1368
        node: Node,
381
1368
        viewport: &cairo::Rectangle,
382
1368
        options: &RenderingOptions,
383
1368
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), InternalRenderingError> {
384
1368
        let viewport = Rect::from(*viewport);
385

            
386
1368
        let (ink_rect, logical_rect) = self.geometry_for_layer(node, viewport, options)?;
387

            
388
1368
        Ok((
389
1368
            cairo::Rectangle::from(ink_rect),
390
1368
            cairo::Rectangle::from(logical_rect),
391
1368
        ))
392
1368
    }
393

            
394
190
    fn get_bbox_for_element(
395
190
        &self,
396
190
        node: &Node,
397
190
        options: &RenderingOptions,
398
190
    ) -> Result<BoundingBox, InternalRenderingError> {
399
190
        let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
400
190
        let cr = cairo::Context::new(&target)?;
401

            
402
190
        let node = node.clone();
403
190

            
404
190
        let config = options.to_rendering_configuration(true);
405
190

            
406
190
        self.draw_tree(DrawingMode::OnlyNode(node), &cr, unit_rectangle(), config)
407
190
    }
408

            
409
    /// Returns (ink_rect, logical_rect)
410
95
    pub fn get_geometry_for_element(
411
95
        &self,
412
95
        node: Node,
413
95
        options: &RenderingOptions,
414
95
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), InternalRenderingError> {
415
95
        let bbox = self.get_bbox_for_element(&node, options)?;
416

            
417
95
        let ink_rect = bbox.ink_rect.unwrap_or_default();
418
95
        let logical_rect = bbox.rect.unwrap_or_default();
419
95

            
420
95
        // Translate so ink_rect is always at offset (0, 0)
421
95
        let ofs = (-ink_rect.x0, -ink_rect.y0);
422
95

            
423
95
        Ok((
424
95
            cairo::Rectangle::from(ink_rect.translate(ofs)),
425
95
            cairo::Rectangle::from(logical_rect.translate(ofs)),
426
95
        ))
427
95
    }
428

            
429
95
    pub fn render_element(
430
95
        &self,
431
95
        cr: &cairo::Context,
432
95
        node: Node,
433
95
        element_viewport: &cairo::Rectangle,
434
95
        options: &RenderingOptions,
435
95
    ) -> Result<(), InternalRenderingError> {
436
95
        cr.status()?;
437

            
438
95
        let bbox = self.get_bbox_for_element(&node, options)?;
439

            
440
95
        if bbox.ink_rect.is_none() || bbox.rect.is_none() {
441
            // Nothing to draw
442
            return Ok(());
443
95
        }
444
95

            
445
95
        let ink_r = bbox.ink_rect.unwrap_or_default();
446
95

            
447
95
        if ink_r.is_empty() {
448
            return Ok(());
449
95
        }
450
95

            
451
95
        // Render, transforming so element is at the new viewport's origin
452
95

            
453
95
        with_saved_cr(cr, || {
454
95
            let factor = (element_viewport.width() / ink_r.width())
455
95
                .min(element_viewport.height() / ink_r.height());
456
95

            
457
95
            cr.translate(element_viewport.x(), element_viewport.y());
458
95
            cr.scale(factor, factor);
459
95
            cr.translate(-ink_r.x0, -ink_r.y0);
460
95

            
461
95
            let config = options.to_rendering_configuration(false);
462
95

            
463
95
            self.draw_tree(DrawingMode::OnlyNode(node), cr, unit_rectangle(), config)
464
95
                .map(|_bbox| ())
465
95
        })
466
95
    }
467

            
468
    /// Wrapper for [`drawing_ctx::draw_tree`].  This just ensures that the document
469
    /// is cascaded before rendering.
470
20653
    fn draw_tree(
471
20653
        &self,
472
20653
        drawing_mode: DrawingMode,
473
20653
        cr: &cairo::Context,
474
20653
        viewport_rect: Rect,
475
20653
        config: RenderingConfiguration,
476
20653
    ) -> Result<BoundingBox, InternalRenderingError> {
477
20653
        self.ensure_is_cascaded();
478
20653

            
479
20653
        let cancellable = config.cancellable.clone();
480
20653

            
481
20653
        draw_tree(
482
20653
            self.session.clone(),
483
20653
            drawing_mode,
484
20653
            cr,
485
20653
            viewport_rect,
486
20653
            config,
487
20653
            &mut AcquiredNodes::new(self, cancellable),
488
20653
        )
489
20653
    }
490

            
491
41165
    fn ensure_is_cascaded(&self) {
492
41165
        if self.needs_cascade.get() {
493
19505
            self.cascade(&[]);
494
21671
        }
495
41165
    }
496
}
497

            
498
285
fn unit_rectangle() -> Rect {
499
285
    Rect::from_size(1.0, 1.0)
500
285
}
501

            
502
/// Any kind of resource loaded while processing an SVG document: images, or SVGs themselves.
503
#[derive(Clone)]
504
pub enum Resource {
505
    Document(Rc<Document>),
506
    Image(SharedImageSurface),
507
}
508

            
509
/// Set of external resources (other SVG documents, or raster images) referenced by an SVG.
510
///
511
/// For example, a PNG image in `<image href="foo.png"/>` gets decoded
512
/// and stored here, referenced by its URL.
513
struct Resources {
514
    resources: HashMap<AllowedUrl, Result<Resource, LoadingError>>,
515
}
516

            
517
impl Resources {
518
19961
    fn new() -> Resources {
519
19961
        Resources {
520
19961
            resources: Default::default(),
521
19961
        }
522
19961
    }
523

            
524
    /// Looks up a specific node by its id in another SVG document.
525
    ///
526
    /// For example, in `<use href="foo.svg#some_node"/>`, or in `filter="url(filters.svg#foo)"`.
527
    ///
528
    /// The URL is not validated yet; this function will take care of that and return a
529
    /// suitable error.
530
3287
    fn lookup_node(
531
3287
        &mut self,
532
3287
        session: &Session,
533
3287
        load_options: &LoadOptions,
534
3287
        url: &str,
535
3287
        id: &str,
536
3287
        cancellable: Option<&gio::Cancellable>,
537
3287
    ) -> Result<Node, LoadingError> {
538
3287
        self.get_extern_document(session, load_options, url, cancellable)
539
3287
            .and_then(|resource| match resource {
540
133
                Resource::Document(doc) => doc.lookup_internal_node(id).ok_or(LoadingError::BadUrl),
541
                _ => unreachable!("get_extern_document() should already have ensured the document"),
542
3287
            })
543
3287
    }
544

            
545
    /// Validates the URL and loads an SVG document as a [`Resource`].
546
    ///
547
    /// The document can then be used whole (`<image href="foo.svg"/>`, or individual
548
    /// elements from it can be looked up (`<use href="foo.svg#some_node"/>`).
549
3287
    fn get_extern_document(
550
3287
        &mut self,
551
3287
        session: &Session,
552
3287
        load_options: &LoadOptions,
553
3287
        href: &str,
554
3287
        cancellable: Option<&gio::Cancellable>,
555
3287
    ) -> Result<Resource, LoadingError> {
556
3287
        let aurl = load_options
557
3287
            .url_resolver
558
3287
            .resolve_href(href)
559
3287
            .map_err(|_| LoadingError::BadUrl)?;
560

            
561
418
        let resource = self.lookup_resource(session, load_options, &aurl, cancellable)?;
562

            
563
133
        match resource {
564
133
            Resource::Document(_) => Ok(resource),
565
            _ => Err(LoadingError::Other(format!(
566
                "{href} is not an SVG document"
567
            ))),
568
        }
569
3287
    }
570

            
571
    /// Loads a resource (an SVG document or a raster image), or returns an already-loaded one.
572
3287
    fn lookup_resource(
573
3287
        &mut self,
574
3287
        session: &Session,
575
3287
        load_options: &LoadOptions,
576
3287
        aurl: &AllowedUrl,
577
3287
        cancellable: Option<&gio::Cancellable>,
578
3287
    ) -> Result<Resource, LoadingError> {
579
3287
        match self.resources.entry(aurl.clone()) {
580
2546
            Entry::Occupied(e) => e.get().clone(),
581

            
582
741
            Entry::Vacant(e) => {
583
741
                let resource_result = load_resource(session, load_options, aurl, cancellable);
584
741
                e.insert(resource_result.clone());
585
741
                resource_result
586
            }
587
        }
588
3287
    }
589
}
590

            
591
/// Loads the entire contents of a URL, sniffs them, and decodes them as a [`Resource`]
592
/// for an SVG or raster image.
593
///
594
/// Assumes that `gio`'s content-sniffing machinery is working correctly.  Anything that
595
/// doesn't sniff like an SVG document will be decoded as a raster image.
596
///
597
/// This handles `data:` URLs correctly, by decoding them into binary data, and then
598
/// sniffing it or using the declared MIME type in the `data:` URL itself.
599
741
fn load_resource(
600
741
    session: &Session,
601
741
    load_options: &LoadOptions,
602
741
    aurl: &AllowedUrl,
603
741
    cancellable: Option<&gio::Cancellable>,
604
741
) -> Result<Resource, LoadingError> {
605
741
    let data = io::acquire_data(aurl, cancellable)?;
606

            
607
741
    let svg_mime_type = Mime::from_str("image/svg+xml").unwrap();
608
741

            
609
741
    if data.mime_type == svg_mime_type {
610
114
        load_svg_resource_from_bytes(session, load_options, aurl, data, cancellable)
611
    } else {
612
627
        load_image_resource_from_bytes(load_options, aurl, data)
613
    }
614
741
}
615

            
616
/// Parses [`BinaryData`] that is known to be an SVG document, using librsvg itself.
617
114
fn load_svg_resource_from_bytes(
618
114
    session: &Session,
619
114
    load_options: &LoadOptions,
620
114
    aurl: &AllowedUrl,
621
114
    data: BinaryData,
622
114
    cancellable: Option<&gio::Cancellable>,
623
114
) -> Result<Resource, LoadingError> {
624
114
    let BinaryData {
625
114
        data: input_bytes,
626
114
        mime_type: _mime_type,
627
114
    } = data;
628
114

            
629
114
    let bytes = glib::Bytes::from_owned(input_bytes);
630
114
    let stream = gio::MemoryInputStream::from_bytes(&bytes);
631

            
632
114
    let document = Document::load_from_stream(
633
114
        session.clone(),
634
114
        Arc::new(load_options.copy_with_base_url(aurl)),
635
114
        &stream.upcast(),
636
114
        cancellable,
637
114
    )?;
638

            
639
114
    document.ensure_is_cascaded();
640
114

            
641
114
    Ok(Resource::Document(Rc::new(document)))
642
114
}
643

            
644
/// Decodes [`BinaryData`] that is presumed to be a raster image.
645
///
646
/// To know which decoder to use (or to even decide if this is a supported image format),
647
/// this function uses the `mime_type` field in the [`BinaryData`].
648
///
649
/// The [`AllowdUrl`] is not used for decoding; it is just to construct an error message
650
/// for the return value.
651
627
fn load_image_resource_from_bytes(
652
627
    load_options: &LoadOptions,
653
627
    aurl: &AllowedUrl,
654
627
    data: BinaryData,
655
627
) -> Result<Resource, LoadingError> {
656
627
    let BinaryData {
657
627
        data: bytes,
658
627
        mime_type,
659
627
    } = data;
660
627

            
661
627
    if bytes.is_empty() {
662
        return Err(LoadingError::Other(String::from("no image data")));
663
627
    }
664
627

            
665
627
    let content_type = content_type_for_image(&mime_type);
666
627

            
667
627
    load_image_with_image_rs(aurl, bytes, content_type, load_options)
668
627
}
669

            
670
/// Decides whether the specified MIME type is supported as a raster image format.
671
///
672
/// Librsvg explicitly only supports PNG/JPEG/GIF/WEBP, and AVIF optionally.  See the
673
/// documentation on [supported raster image formats][formats] for details.
674
///
675
/// [formats]: https://gnome.pages.gitlab.gnome.org/librsvg/devel-docs/features.html#supported-raster-image-formats
676
532
fn image_format(content_type: &str) -> Result<image::ImageFormat, LoadingError> {
677
532
    match content_type {
678
532
        "image/png" => Ok(image::ImageFormat::Png),
679
114
        "image/jpeg" => Ok(image::ImageFormat::Jpeg),
680
        "image/gif" => Ok(image::ImageFormat::Gif),
681
        "image/webp" => Ok(image::ImageFormat::WebP),
682

            
683
        #[cfg(feature = "avif")]
684
        "image/avif" => Ok(image::ImageFormat::Avif),
685

            
686
        _ => Err(LoadingError::Other(format!(
687
            "unsupported image format {content_type}"
688
        ))),
689
    }
690
532
}
691

            
692
627
fn load_image_with_image_rs(
693
627
    aurl: &AllowedUrl,
694
627
    bytes: Vec<u8>,
695
627
    content_type: Option<String>,
696
627
    load_options: &LoadOptions,
697
627
) -> Result<Resource, LoadingError> {
698
627
    let cursor = Cursor::new(&bytes);
699

            
700
627
    let reader = if let Some(ref content_type) = content_type {
701
532
        let format = image_format(content_type)?;
702
532
        image::ImageReader::with_format(cursor, format)
703
    } else {
704
95
        image::ImageReader::new(cursor)
705
95
            .with_guessed_format()
706
95
            .map_err(|_| LoadingError::Other(String::from("unknown image format")))?
707
    };
708

            
709
627
    let image = reader
710
627
        .decode()
711
627
        .map_err(|e| LoadingError::Other(format!("error decoding image: {e}")))?;
712

            
713
570
    let bytes = if load_options.keep_image_data {
714
        Some(bytes)
715
    } else {
716
570
        None
717
    };
718

            
719
570
    let surface = SharedImageSurface::from_image(&image, content_type.as_deref(), bytes)
720
570
        .map_err(|e| image_loading_error_from_cairo(e, aurl))?;
721

            
722
570
    Ok(Resource::Image(surface))
723
627
}
724

            
725
629
fn content_type_for_image(mime_type: &Mime) -> Option<String> {
726
629
    // See issue #548 - data: URLs without a MIME-type automatically
727
629
    // fall back to "text/plain;charset=US-ASCII".  Some (old?) versions of
728
629
    // Adobe Illustrator generate data: URLs without MIME-type for image
729
629
    // data.  We'll catch this and fall back to sniffing by unsetting the
730
629
    // content_type.
731
629
    let unspecified_mime_type = Mime::from_str("text/plain;charset=US-ASCII").unwrap();
732
629

            
733
629
    if *mime_type == unspecified_mime_type {
734
96
        None
735
    } else {
736
533
        Some(format!("{}/{}", mime_type.type_, mime_type.subtype))
737
    }
738
629
}
739

            
740
/// Formats a URL for human consumption, as in error messages.  This is to
741
/// reduce very long `data:` URLs to an abbreviated version.
742
fn human_readable_url(aurl: &AllowedUrl) -> &str {
743
    if aurl.scheme() == "data" {
744
        // avoid printing a huge data: URL for image data
745
        "data URL"
746
    } else {
747
        aurl.as_ref()
748
    }
749
}
750

            
751
/// Converts a `cairo::Error` that happened while wrapping a decoded raster image
752
/// into a `LoadingError` augmented with the image's URL.
753
fn image_loading_error_from_cairo(status: cairo::Error, aurl: &AllowedUrl) -> LoadingError {
754
    let url = human_readable_url(aurl);
755

            
756
    match status {
757
        cairo::Error::NoMemory => LoadingError::OutOfMemory(format!("loading image: {url}")),
758
        cairo::Error::InvalidSize => LoadingError::Other(format!("image too big: {url}")),
759
        _ => LoadingError::Other(format!("cairo error: {status}")),
760
    }
761
}
762

            
763
pub struct AcquiredNode {
764
    stack: Option<Rc<RefCell<NodeStack>>>,
765
    node: Node,
766
}
767

            
768
impl Drop for AcquiredNode {
769
38029909
    fn drop(&mut self) {
770
38029909
        if let Some(ref stack) = self.stack {
771
28525102
            let mut stack = stack.borrow_mut();
772
28525102
            let last = stack.pop().unwrap();
773
28525102
            assert!(last == self.node);
774
9504807
        }
775
38029909
    }
776
}
777

            
778
impl AcquiredNode {
779
28524969
    pub fn get(&self) -> &Node {
780
28524969
        &self.node
781
28524969
    }
782
}
783

            
784
/// Detects circular references between nodes, and enforces referencing limits.
785
///
786
/// Consider this fragment of SVG:
787
///
788
/// ```xml
789
/// <pattern id="foo">
790
///   <rect width="1" height="1" fill="url(#foo)"/>
791
/// </pattern>
792
/// ```
793
///
794
/// The pattern has a child element that references the pattern itself.  This kind of circular
795
/// reference is invalid.  The `AcquiredNodes` struct is passed around
796
/// wherever it may be necessary to resolve references to nodes, or to access nodes
797
/// "elsewhere" in the DOM that is not the current subtree.
798
///
799
/// Also, such constructs that reference other elements can be maliciously arranged like
800
/// in the [billion laughs attack][lol], to cause huge amounts of CPU to be consumed through
801
/// creating an exponential number of references.  `AcquiredNodes` imposes a hard limit on
802
/// the number of references that can be resolved for typical, well-behaved SVG documents.
803
///
804
/// The [`Self::acquire()`] and [`Self::acquire_ref()`] methods return an [`AcquiredNode`], which
805
/// acts like a smart pointer for a [`Node`].  Once a node has been acquired, it cannot be
806
/// acquired again until its [`AcquiredNode`] is dropped.  In the example above, a graphic element
807
/// would acquire the `pattern`, which would then acquire its `rect` child, which then would fail
808
/// to re-acquired the `pattern` — thus signaling a circular reference.
809
///
810
/// Those methods return an [`AcquireError`] to signal circular references.  Also, they
811
/// can return [`AcquireError::MaxReferencesExceeded`] if the aforementioned referencing limit
812
/// is exceeded.
813
///
814
/// [lol]: https://bitbucket.org/tiran/defusedxml
815
pub struct AcquiredNodes<'i> {
816
    document: &'i Document,
817
    num_elements_acquired: usize,
818
    node_stack: Rc<RefCell<NodeStack>>,
819
    nodes_with_cycles: Vec<Node>,
820
    cancellable: Option<gio::Cancellable>,
821
}
822

            
823
impl<'i> AcquiredNodes<'i> {
824
20659
    pub fn new(document: &Document, cancellable: Option<gio::Cancellable>) -> AcquiredNodes<'_> {
825
20659
        AcquiredNodes {
826
20659
            document,
827
20659
            num_elements_acquired: 0,
828
20659
            node_stack: Rc::new(RefCell::new(NodeStack::new())),
829
20659
            nodes_with_cycles: Vec::new(),
830
20659
            cancellable,
831
20659
        }
832
20659
    }
833

            
834
2888
    pub fn lookup_resource(&self, url: &str) -> Result<Resource, LoadingError> {
835
2888
        self.document
836
2888
            .lookup_resource(url, self.cancellable.as_ref())
837
2888
    }
838

            
839
    /// Acquires a node by its id.
840
    ///
841
    /// This is typically used during an "early resolution" stage, when XML `id`s are being
842
    /// resolved to node references.
843
19023430
    pub fn acquire(&mut self, node_id: &NodeId) -> Result<AcquiredNode, AcquireError> {
844
19023430
        self.num_elements_acquired += 1;
845
19023430

            
846
19023430
        // This is a mitigation for SVG files that try to instance a huge number of
847
19023430
        // elements via <use>, recursive patterns, etc.  See limits.rs for details.
848
19023430
        if self.num_elements_acquired > limits::MAX_REFERENCED_ELEMENTS {
849
247
            return Err(AcquireError::MaxReferencesExceeded);
850
19023183
        }
851

            
852
        // FIXME: callers shouldn't have to know that get_node() can initiate a file load.
853
        // Maybe we should have the following stages:
854
        //   - load main SVG XML
855
        //
856
        //   - load secondary resources: SVG XML and other files like images
857
        //
858
        //   - Now that all files are loaded, resolve URL references
859
19023183
        let node = self
860
19023183
            .document
861
19023183
            .lookup_node(node_id, self.cancellable.as_ref())
862
19023183
            .ok_or_else(|| AcquireError::LinkNotFound(node_id.clone()))?;
863

            
864
19019896
        if self.nodes_with_cycles.contains(&node) {
865
304
            return Err(AcquireError::CircularReference(node.clone()));
866
19019592
        }
867
19019592

            
868
19019592
        if node.borrow_element().is_accessed_by_reference() {
869
9514785
            self.acquire_ref(&node)
870
        } else {
871
9504807
            Ok(AcquiredNode { stack: None, node })
872
        }
873
19023430
    }
874

            
875
    /// Acquires a node whose reference is already known.
876
    ///
877
    /// This is useful for cases where a node is initially referenced by its id with
878
    /// [`Self::acquire`] and kept around for later use.  During the later use, the node
879
    /// needs to be re-acquired with this method.  For example:
880
    ///
881
    /// * At an "early resolution" stage, `acquire()` a pattern by its id, and keep around its
882
    ///   [`Node`] reference.
883
    ///
884
    /// * At the drawing stage, `acquire_ref()` the pattern node that we already had, so that
885
    ///   its child elements that reference other paint servers will be able to detect circular
886
    ///   references to the pattern.
887
28527857
    pub fn acquire_ref(&mut self, node: &Node) -> Result<AcquiredNode, AcquireError> {
888
28527857
        if self.nodes_with_cycles.contains(node) {
889
2299
            Err(AcquireError::CircularReference(node.clone()))
890
28525558
        } else if self.node_stack.borrow().contains(node) {
891
456
            self.nodes_with_cycles.push(node.clone());
892
456
            Err(AcquireError::CircularReference(node.clone()))
893
        } else {
894
28525102
            self.node_stack.borrow_mut().push(node);
895
28525102
            Ok(AcquiredNode {
896
28525102
                stack: Some(self.node_stack.clone()),
897
28525102
                node: node.clone(),
898
28525102
            })
899
        }
900
28527857
    }
901
}
902

            
903
/// Keeps a stack of nodes and can check if a certain node is contained in the stack
904
///
905
/// Sometimes parts of the code cannot plainly use the implicit stack of acquired
906
/// nodes as maintained by DrawingCtx::acquire_node(), and they must keep their
907
/// own stack of nodes to test for reference cycles.  NodeStack can be used to do that.
908
pub struct NodeStack(Vec<Node>);
909

            
910
impl NodeStack {
911
9524763
    pub fn new() -> NodeStack {
912
9524763
        NodeStack(Vec::new())
913
9524763
    }
914

            
915
28525634
    pub fn push(&mut self, node: &Node) {
916
28525634
        self.0.push(node.clone());
917
28525634
    }
918

            
919
28525102
    pub fn pop(&mut self) -> Option<Node> {
920
28525102
        self.0.pop()
921
28525102
    }
922

            
923
28526109
    pub fn contains(&self, node: &Node) -> bool {
924
28526109
        self.0.contains(node)
925
28526109
    }
926
}
927

            
928
/// Used to build a tree of SVG nodes while an XML document is being read.
929
///
930
/// This struct holds the document-related state while loading an SVG document from XML:
931
/// the loading options, the partially-built tree of nodes, the CSS stylesheets that
932
/// appear while loading the document.
933
///
934
/// The XML loader asks a `DocumentBuilder` to
935
/// [`append_element`][DocumentBuilder::append_element],
936
/// [`append_characters`][DocumentBuilder::append_characters], etc.  When all the XML has
937
/// been consumed, the caller can use [`build`][DocumentBuilder::build] to get a
938
/// fully-loaded [`Document`].
939
pub struct DocumentBuilder {
940
    /// Metadata for the document's lifetime.
941
    session: Session,
942

            
943
    /// Loading options; mainly the URL resolver.
944
    load_options: Arc<LoadOptions>,
945

            
946
    /// Root node of the tree.
947
    tree: Option<Node>,
948

            
949
    /// Mapping from `id` attributes to nodes.
950
    ids: HashMap<String, Node>,
951

            
952
    /// Stylesheets defined in the document.
953
    stylesheets: Vec<Stylesheet>,
954
}
955

            
956
impl DocumentBuilder {
957
20284
    pub fn new(session: Session, load_options: Arc<LoadOptions>) -> DocumentBuilder {
958
20284
        DocumentBuilder {
959
20284
            session,
960
20284
            load_options,
961
20284
            tree: None,
962
20284
            ids: HashMap::new(),
963
20284
            stylesheets: Vec::new(),
964
20284
        }
965
20284
    }
966

            
967
    /// Adds a stylesheet in order to the document.
968
    ///
969
    /// Stylesheets will later be matched in the order in which they were added.
970
817
    pub fn append_stylesheet(&mut self, stylesheet: Stylesheet) {
971
817
        self.stylesheets.push(stylesheet);
972
817
    }
973

            
974
    /// Creates an element of the specified `name` as a child of `parent`.
975
    ///
976
    /// This is the main function to create new SVG elements while parsing XML.
977
    ///
978
    /// `name` is the XML element's name, for example `rect`.
979
    ///
980
    /// `attrs` has the XML element's attributes, e.g. cx/cy/r for `<circle cx="0" cy="0"
981
    /// r="5">`.
982
    ///
983
    /// If `parent` is `None` it means that we are creating the root node in the tree of
984
    /// elements.  The code will later validate that this is indeed an `<svg>` element.
985
19460491
    pub fn append_element(
986
19460491
        &mut self,
987
19460491
        name: &QualName,
988
19460491
        attrs: Attributes,
989
19460491
        parent: Option<Node>,
990
19460491
    ) -> Node {
991
19460491
        let node = Node::new(NodeData::new_element(&self.session, name, attrs));
992

            
993
19460491
        if let Some(id) = node.borrow_element().get_id() {
994
221356
            match self.ids.entry(id.to_string()) {
995
                Entry::Occupied(_) => {
996
532
                    rsvg_log!(self.session, "ignoring duplicate id {id} for {node}");
997
                }
998

            
999
220824
                Entry::Vacant(e) => {
220824
                    e.insert(node.clone());
220824
                }
            }
19239135
        }
19460491
        if let Some(parent) = parent {
19440359
            parent.append(node.clone());
19440359
        } else if self.tree.is_none() {
20132
            self.tree = Some(node.clone());
20132
        } else {
            panic!("The tree root has already been set");
        }
19460491
        node
19460491
    }
    /// Creates a node for an XML text element as a child of `parent`.
19684817
    pub fn append_characters(&mut self, text: &str, parent: &mut Node) {
19684817
        if !text.is_empty() {
            // When the last child is a Chars node we can coalesce
            // the text and avoid screwing up the Pango layouts
19684817
            if let Some(child) = parent.last_child().filter(|c| c.is_chars()) {
47961
                child.borrow_chars().append(text);
19636856
            } else {
19636856
                parent.append(Node::new(NodeData::new_chars(text)));
19636856
            };
        }
19684817
    }
    /// Does the final validation on the `Document` being read, and returns it.
20018
    pub fn build(self) -> Result<Document, LoadingError> {
20018
        let DocumentBuilder {
20018
            load_options,
20018
            session,
20018
            tree,
20018
            ids,
20018
            stylesheets,
20018
            ..
20018
        } = self;
20018
        match tree {
20018
            Some(root) if root.is_element() => {
20018
                if is_element_of_type!(root, Svg) {
19961
                    let document = Document {
19961
                        tree: RefCell::new(root),
19961
                        session: session.clone(),
19961
                        ids,
19961
                        resources: RefCell::new(Resources::new()),
19961
                        load_options,
19961
                        stylesheets,
19961
                        needs_cascade: Cell::new(true),
19961
                    };
19961

            
19961
                    Ok(document)
                } else {
57
                    Err(LoadingError::NoSvgRoot)
                }
            }
            _ => Err(LoadingError::NoSvgRoot),
        }
20018
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
1
    fn parses_node_id() {
1
        assert_eq!(
1
            NodeId::parse("#foo").unwrap(),
1
            NodeId::Internal("foo".to_string())
1
        );
1
        assert_eq!(
1
            NodeId::parse("uri#foo").unwrap(),
1
            NodeId::External("uri".to_string(), "foo".to_string())
1
        );
1
        assert!(matches!(
1
            NodeId::parse("uri"),
            Err(NodeIdError::NodeIdRequired)
        ));
1
    }
    #[test]
1
    fn unspecified_mime_type_yields_no_content_type() {
1
        // Issue #548
1
        let mime = Mime::from_str("text/plain;charset=US-ASCII").unwrap();
1
        assert!(content_type_for_image(&mime).is_none());
1
    }
    #[test]
1
    fn strips_mime_type_parameters() {
1
        // Issue #699
1
        let mime = Mime::from_str("image/png;charset=utf-8").unwrap();
1
        assert_eq!(
1
            content_type_for_image(&mime),
1
            Some(String::from("image/png"))
1
        );
1
    }
}