1
//! Main SVG document structure.
2

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

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

            
34
/// Identifier of a node
35
505833
#[derive(Debug, PartialEq, Clone)]
36
pub enum NodeId {
37
    /// element id
38
505789
    Internal(String),
39
    /// url, element id
40
17
    External(String, String),
41
}
42

            
43
impl NodeId {
44
1516
    pub fn parse(href: &str) -> Result<NodeId, NodeIdError> {
45
3032
        let (url, id) = match href.rfind('#') {
46
47
            None => (Some(href), None),
47
1448
            Some(0) => (None, Some(&href[1..])),
48
21
            Some(p) => (Some(&href[..p]), Some(&href[(p + 1)..])),
49
        };
50

            
51
1516
        match (url, id) {
52
1448
            (None, Some(id)) if !id.is_empty() => Ok(NodeId::Internal(String::from(id))),
53
21
            (Some(url), Some(id)) if !id.is_empty() => {
54
21
                Ok(NodeId::External(String::from(url), String::from(id)))
55
21
            }
56
47
            _ => Err(NodeIdError::NodeIdRequired),
57
        }
58
1516
    }
59
}
60

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

            
70
/// Loading options for SVG documents.
71
pub struct LoadOptions {
72
    /// Load url resolver; all references will be resolved with respect to this.
73
    pub url_resolver: UrlResolver,
74

            
75
    /// Whether to turn off size limits in libxml2.
76
    pub unlimited_size: bool,
77

            
78
    /// Whether to keep original (undecoded) image data to embed in Cairo PDF surfaces.
79
    pub keep_image_data: bool,
80
}
81

            
82
impl LoadOptions {
83
    /// Creates a `LoadOptions` with defaults, and sets the `url resolver`.
84
1089
    pub fn new(url_resolver: UrlResolver) -> Self {
85
1089
        LoadOptions {
86
            url_resolver,
87
            unlimited_size: false,
88
            keep_image_data: false,
89
        }
90
1089
    }
91

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

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

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

            
118
5
        LoadOptions {
119
5
            url_resolver,
120
5
            unlimited_size: self.unlimited_size,
121
5
            keep_image_data: self.keep_image_data,
122
        }
123
5
    }
124
}
125

            
126
/// A loaded SVG file and its derived data.
127
pub struct Document {
128
    /// Tree of nodes; the root is guaranteed to be an `<svg>` element.
129
    tree: Node,
130

            
131
    /// Metadata about the SVG handle.
132
    session: Session,
133

            
134
    /// Mapping from `id` attributes to nodes.
135
    ids: HashMap<String, Node>,
136

            
137
    /// Othewr SVG documents and images referenced from this document.
138
    ///
139
    /// This requires requires interior mutability because we load resources all over the
140
    /// place.  Eventually we'll be able to do this once, at loading time, and keep this
141
    /// immutable.
142
    resources: RefCell<Resources>,
143

            
144
    /// Used to load referenced resources.
145
    load_options: Arc<LoadOptions>,
146

            
147
    /// Stylesheets defined in the document.
148
    stylesheets: Vec<Stylesheet>,
149
}
150

            
151
impl Document {
152
    /// Constructs a `Document` by loading it from a stream.
153
1110
    pub fn load_from_stream(
154
        session: Session,
155
        load_options: Arc<LoadOptions>,
156
        stream: &gio::InputStream,
157
        cancellable: Option<&gio::Cancellable>,
158
    ) -> Result<Document, LoadingError> {
159
1110
        xml_load_from_possibly_compressed_stream(
160
1110
            session.clone(),
161
1110
            DocumentBuilder::new(session, load_options.clone()),
162
1110
            load_options,
163
            stream,
164
            cancellable,
165
1110
        )
166
1110
    }
167

            
168
    /// Utility function to load a document from a static string in tests.
169
    #[cfg(test)]
170
5
    pub fn load_from_bytes(input: &'static [u8]) -> Document {
171
5
        let bytes = glib::Bytes::from_static(input);
172
5
        let stream = gio::MemoryInputStream::from_bytes(&bytes);
173

            
174
5
        Document::load_from_stream(
175
5
            Session::new_for_test_suite(),
176
5
            Arc::new(LoadOptions::new(UrlResolver::new(None))),
177
5
            &stream.upcast(),
178
5
            None::<&gio::Cancellable>,
179
        )
180
        .unwrap()
181
5
    }
182

            
183
    /// Gets the root node.  This is guaranteed to be an `<svg>` element.
184
4555
    pub fn root(&self) -> Node {
185
4555
        self.tree.clone()
186
4555
    }
187

            
188
    /// Looks up a node in this document or one of its resources by its `id` attribute.
189
1000326
    fn lookup_node(&self, node_id: &NodeId) -> Option<Node> {
190
1000326
        match node_id {
191
1000314
            NodeId::Internal(id) => self.lookup_internal_node(id),
192
24
            NodeId::External(url, id) => self
193
                .resources
194
                .borrow_mut()
195
12
                .lookup_node(&self.session, &self.load_options, url, id)
196
12
                .ok(),
197
        }
198
1000326
    }
199

            
200
    /// Looks up a node in this document by its `id` attribute.
201
1000288
    pub fn lookup_internal_node(&self, id: &str) -> Option<Node> {
202
2000354
        self.ids.get(id).map(|n| (*n).clone())
203
1000288
    }
204

            
205
    /// Loads an image by URL, or returns a pre-loaded one.
206
41
    fn lookup_image(&self, url: &str) -> Result<SharedImageSurface, LoadingError> {
207
41
        let aurl = self
208
            .load_options
209
            .url_resolver
210
            .resolve_href(url)
211
            .map_err(|_| LoadingError::BadUrl)?;
212

            
213
82
        self.resources
214
            .borrow_mut()
215
41
            .lookup_image(&self.session, &self.load_options, &aurl)
216
41
    }
217

            
218
    /// Loads a resource by URL, or returns a pre-loaded one.
219
110
    fn lookup_resource(&self, url: &str) -> Result<Resource, LoadingError> {
220
110
        let aurl = self
221
            .load_options
222
            .url_resolver
223
            .resolve_href(url)
224
2
            .map_err(|_| LoadingError::BadUrl)?;
225

            
226
        // FIXME: pass a cancellable to this.  This function is called
227
        // at rendering time, so probably the cancellable should come
228
        // from cancellability in CairoRenderer - see #429
229
218
        self.resources
230
            .borrow_mut()
231
109
            .lookup_resource(&self.session, &self.load_options, &aurl, None)
232
110
    }
233

            
234
    /// Runs the CSS cascade on the document tree
235
    ///
236
    /// This uses the default UserAgent stylesheet, the document's internal stylesheets,
237
    /// plus an extra set of stylesheets supplied by the caller.
238
1100
    pub fn cascade(&mut self, extra: &[Stylesheet], session: &Session) {
239
        let stylesheets = {
240
            static UA_STYLESHEETS: OnceLock<Vec<Stylesheet>> = OnceLock::new();
241
1212
            UA_STYLESHEETS.get_or_init(|| {
242
224
                vec![Stylesheet::from_data(
243
                    include_str!("ua.css"),
244
112
                    &UrlResolver::new(None),
245
112
                    Origin::UserAgent,
246
112
                    Session::default(),
247
                )
248
                .expect("could not parse user agent stylesheet for librsvg, there's a bug!")]
249
112
            })
250
        };
251
1100
        css::cascade(
252
1100
            &mut self.tree,
253
1100
            stylesheets,
254
1100
            &self.stylesheets,
255
            extra,
256
            session,
257
        );
258
1100
    }
259

            
260
1152
    pub fn get_intrinsic_dimensions(&self) -> IntrinsicDimensions {
261
1152
        let root = self.root();
262
1152
        let cascaded = CascadedValues::new_from_node(&root);
263
1152
        let values = cascaded.get();
264
1152
        borrow_element_as!(self.root(), Svg).get_intrinsic_dimensions(values)
265
1150
    }
266

            
267
988
    pub fn render_document(
268
        &self,
269
        session: &Session,
270
        cr: &cairo::Context,
271
        viewport: &cairo::Rectangle,
272
        user_language: &UserLanguage,
273
        dpi: Dpi,
274
        svg_nesting: SvgNesting,
275
        is_testing: bool,
276
    ) -> Result<(), InternalRenderingError> {
277
988
        let root = self.root();
278
988
        self.render_layer(
279
            session,
280
            cr,
281
            root,
282
            viewport,
283
            user_language,
284
            dpi,
285
            svg_nesting,
286
            is_testing,
287
        )
288
988
    }
289

            
290
1003
    pub fn render_layer(
291
        &self,
292
        session: &Session,
293
        cr: &cairo::Context,
294
        node: Node,
295
        viewport: &cairo::Rectangle,
296
        user_language: &UserLanguage,
297
        dpi: Dpi,
298
        svg_nesting: SvgNesting,
299
        is_testing: bool,
300
    ) -> Result<(), InternalRenderingError> {
301
1003
        cr.status()?;
302

            
303
1001
        let root = self.root();
304

            
305
1003
        let viewport = Rect::from(*viewport);
306

            
307
2009
        with_saved_cr(cr, || {
308
2004
            draw_tree(
309
1002
                session.clone(),
310
1002
                DrawingMode::LimitToStack { node, root },
311
1002
                cr,
312
1002
                viewport,
313
1002
                user_language,
314
1002
                dpi,
315
1002
                svg_nesting,
316
                false,
317
1002
                is_testing,
318
1002
                &mut AcquiredNodes::new(self),
319
            )
320
1002
            .map(|_bbox| ())
321
1002
        })
322
1003
    }
323

            
324
84
    fn geometry_for_layer(
325
        &self,
326
        session: &Session,
327
        node: Node,
328
        viewport: Rect,
329
        user_language: &UserLanguage,
330
        dpi: Dpi,
331
        is_testing: bool,
332
    ) -> Result<(Rect, Rect), InternalRenderingError> {
333
84
        let root = self.root();
334

            
335
84
        let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
336
84
        let cr = cairo::Context::new(&target)?;
337

            
338
83
        let bbox = draw_tree(
339
83
            session.clone(),
340
83
            DrawingMode::LimitToStack { node, root },
341
            &cr,
342
            viewport,
343
            user_language,
344
            dpi,
345
83
            SvgNesting::Standalone,
346
            true,
347
            is_testing,
348
83
            &mut AcquiredNodes::new(self),
349
83
        )?;
350

            
351
83
        let ink_rect = bbox.ink_rect.unwrap_or_default();
352
83
        let logical_rect = bbox.rect.unwrap_or_default();
353

            
354
85
        Ok((ink_rect, logical_rect))
355
83
    }
356

            
357
84
    pub fn get_geometry_for_layer(
358
        &self,
359
        session: &Session,
360
        node: Node,
361
        viewport: &cairo::Rectangle,
362
        user_language: &UserLanguage,
363
        dpi: Dpi,
364
        is_testing: bool,
365
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), InternalRenderingError> {
366
84
        let viewport = Rect::from(*viewport);
367

            
368
84
        let (ink_rect, logical_rect) =
369
84
            self.geometry_for_layer(session, node, viewport, user_language, dpi, is_testing)?;
370

            
371
84
        Ok((
372
84
            cairo::Rectangle::from(ink_rect),
373
84
            cairo::Rectangle::from(logical_rect),
374
        ))
375
84
    }
376

            
377
12
    fn get_bbox_for_element(
378
        &self,
379
        session: &Session,
380
        node: &Node,
381
        user_language: &UserLanguage,
382
        dpi: Dpi,
383
        is_testing: bool,
384
    ) -> Result<BoundingBox, InternalRenderingError> {
385
12
        let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
386
12
        let cr = cairo::Context::new(&target)?;
387

            
388
12
        let node = node.clone();
389

            
390
12
        draw_tree(
391
12
            session.clone(),
392
12
            DrawingMode::OnlyNode(node),
393
            &cr,
394
12
            unit_rectangle(),
395
            user_language,
396
            dpi,
397
12
            SvgNesting::Standalone,
398
            true,
399
            is_testing,
400
12
            &mut AcquiredNodes::new(self),
401
        )
402
12
    }
403

            
404
    /// Returns (ink_rect, logical_rect)
405
6
    pub fn get_geometry_for_element(
406
        &self,
407
        session: &Session,
408
        node: Node,
409
        user_language: &UserLanguage,
410
        dpi: Dpi,
411
        is_testing: bool,
412
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), InternalRenderingError> {
413
6
        let bbox = self.get_bbox_for_element(session, &node, user_language, dpi, is_testing)?;
414

            
415
6
        let ink_rect = bbox.ink_rect.unwrap_or_default();
416
6
        let logical_rect = bbox.rect.unwrap_or_default();
417

            
418
        // Translate so ink_rect is always at offset (0, 0)
419
6
        let ofs = (-ink_rect.x0, -ink_rect.y0);
420

            
421
6
        Ok((
422
6
            cairo::Rectangle::from(ink_rect.translate(ofs)),
423
6
            cairo::Rectangle::from(logical_rect.translate(ofs)),
424
        ))
425
6
    }
426

            
427
6
    pub fn render_element(
428
        &self,
429
        session: &Session,
430
        cr: &cairo::Context,
431
        node: Node,
432
        element_viewport: &cairo::Rectangle,
433
        user_language: &UserLanguage,
434
        dpi: Dpi,
435
        is_testing: bool,
436
    ) -> Result<(), InternalRenderingError> {
437
6
        cr.status()?;
438

            
439
6
        let bbox = self.get_bbox_for_element(session, &node, user_language, dpi, is_testing)?;
440

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

            
446
6
        let ink_r = bbox.ink_rect.unwrap_or_default();
447

            
448
6
        if ink_r.is_empty() {
449
            return Ok(());
450
        }
451

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

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

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

            
462
6
            draw_tree(
463
6
                session.clone(),
464
6
                DrawingMode::OnlyNode(node),
465
6
                cr,
466
6
                unit_rectangle(),
467
6
                user_language,
468
6
                dpi,
469
6
                SvgNesting::Standalone,
470
                false,
471
6
                is_testing,
472
6
                &mut AcquiredNodes::new(self),
473
            )
474
6
            .map(|_bbox| ())
475
6
        })
476
6
    }
477
}
478

            
479
18
fn unit_rectangle() -> Rect {
480
18
    Rect::from_size(1.0, 1.0)
481
18
}
482

            
483
/// Any kind of resource loaded while processing an SVG document: images, or SVGs themselves.
484
156
#[derive(Clone)]
485
pub enum Resource {
486
21
    Document(Rc<Document>),
487
135
    Image(SharedImageSurface),
488
}
489

            
490
struct Resources {
491
    resources: HashMap<AllowedUrl, Result<Resource, LoadingError>>,
492
}
493

            
494
impl Resources {
495
1097
    fn new() -> Resources {
496
1097
        Resources {
497
1097
            resources: Default::default(),
498
        }
499
1097
    }
500

            
501
12
    fn lookup_node(
502
        &mut self,
503
        session: &Session,
504
        load_options: &LoadOptions,
505
        url: &str,
506
        id: &str,
507
    ) -> Result<Node, LoadingError> {
508
24
        self.get_extern_document(session, load_options, url)
509
19
            .and_then(|resource| match resource {
510
7
                Resource::Document(doc) => doc.lookup_internal_node(id).ok_or(LoadingError::BadUrl),
511
                _ => unreachable!("get_extern_document() should already have ensured the document"),
512
7
            })
513
12
    }
514

            
515
12
    fn get_extern_document(
516
        &mut self,
517
        session: &Session,
518
        load_options: &LoadOptions,
519
        href: &str,
520
    ) -> Result<Resource, LoadingError> {
521
24
        let aurl = load_options
522
            .url_resolver
523
12
            .resolve_href(href)
524
8
            .map_err(|_| LoadingError::BadUrl)?;
525

            
526
        // FIXME: pass a cancellable to this.  This function is called
527
        // at rendering time, so probably the cancellable should come
528
        // from cancellability in CairoRenderer - see #429
529
8
        let resource = self.lookup_resource(session, load_options, &aurl, None)?;
530

            
531
7
        match resource {
532
7
            Resource::Document(_) => Ok(resource),
533
            _ => Err(LoadingError::Other(format!(
534
                "{href} is not an SVG document"
535
            ))),
536
        }
537
12
    }
538

            
539
41
    fn lookup_image(
540
        &mut self,
541
        session: &Session,
542
        load_options: &LoadOptions,
543
        aurl: &AllowedUrl,
544
    ) -> Result<SharedImageSurface, LoadingError> {
545
        // FIXME: pass a cancellable to this.  This function is called
546
        // at rendering time, so probably the cancellable should come
547
        // from cancellability in CairoRenderer - see #429
548
41
        let resource = self.lookup_resource(session, load_options, aurl, None)?;
549

            
550
41
        match resource {
551
41
            Resource::Image(image) => Ok(image),
552
            _ => Err(LoadingError::Other(format!("{aurl} is not a raster image"))),
553
        }
554
41
    }
555

            
556
158
    fn lookup_resource(
557
        &mut self,
558
        session: &Session,
559
        load_options: &LoadOptions,
560
        aurl: &AllowedUrl,
561
        cancellable: Option<&gio::Cancellable>,
562
    ) -> Result<Resource, LoadingError> {
563
158
        match self.resources.entry(aurl.clone()) {
564
121
            Entry::Occupied(e) => e.get().clone(),
565

            
566
37
            Entry::Vacant(e) => {
567
37
                let resource_result = load_resource(session, load_options, aurl, cancellable);
568
37
                e.insert(resource_result.clone());
569
37
                resource_result
570
37
            }
571
        }
572
158
    }
573
}
574

            
575
37
fn load_resource(
576
    session: &Session,
577
    load_options: &LoadOptions,
578
    aurl: &AllowedUrl,
579
    cancellable: Option<&gio::Cancellable>,
580
) -> Result<Resource, LoadingError> {
581
37
    let data = io::acquire_data(aurl, cancellable)?;
582

            
583
37
    let svg_mime_type = Mime::from_str("image/svg+xml").unwrap();
584

            
585
37
    if data.mime_type == svg_mime_type {
586
5
        load_svg_resource_from_bytes(session, load_options, aurl, data, cancellable)
587
    } else {
588
32
        load_image_resource_from_bytes(load_options, aurl, data)
589
    }
590
37
}
591

            
592
5
fn load_svg_resource_from_bytes(
593
    session: &Session,
594
    load_options: &LoadOptions,
595
    aurl: &AllowedUrl,
596
    data: BinaryData,
597
    cancellable: Option<&gio::Cancellable>,
598
) -> Result<Resource, LoadingError> {
599
    let BinaryData {
600
5
        data: input_bytes,
601
5
        mime_type: _mime_type,
602
5
    } = data;
603

            
604
5
    let bytes = glib::Bytes::from_owned(input_bytes);
605
5
    let stream = gio::MemoryInputStream::from_bytes(&bytes);
606

            
607
5
    let document = Document::load_from_stream(
608
5
        session.clone(),
609
5
        Arc::new(load_options.copy_with_base_url(aurl)),
610
5
        &stream.upcast(),
611
        cancellable,
612
5
    )?;
613

            
614
5
    Ok(Resource::Document(Rc::new(document)))
615
5
}
616

            
617
32
fn load_image_resource_from_bytes(
618
    load_options: &LoadOptions,
619
    aurl: &AllowedUrl,
620
    data: BinaryData,
621
) -> Result<Resource, LoadingError> {
622
    let BinaryData {
623
32
        data: bytes,
624
32
        mime_type,
625
32
    } = data;
626

            
627
32
    if bytes.is_empty() {
628
        return Err(LoadingError::Other(String::from("no image data")));
629
    }
630

            
631
32
    let content_type = content_type_for_image(&mime_type);
632

            
633
32
    load_image_with_image_rs(aurl, bytes, content_type, load_options)
634
32
}
635

            
636
28
fn image_format(content_type: &str) -> Result<image::ImageFormat, LoadingError> {
637
    match content_type {
638
28
        "image/png" => Ok(image::ImageFormat::Png),
639
6
        "image/jpeg" => Ok(image::ImageFormat::Jpeg),
640
        "image/gif" => Ok(image::ImageFormat::Gif),
641
        "image/webp" => Ok(image::ImageFormat::WebP),
642

            
643
        #[cfg(feature = "avif")]
644
        "image/avif" => Ok(image::ImageFormat::Avif),
645

            
646
        _ => Err(LoadingError::Other(format!(
647
            "unsupported image format {content_type}"
648
        ))),
649
    }
650
28
}
651

            
652
32
fn load_image_with_image_rs(
653
    aurl: &AllowedUrl,
654
    bytes: Vec<u8>,
655
    content_type: Option<String>,
656
    load_options: &LoadOptions,
657
) -> Result<Resource, LoadingError> {
658
32
    let cursor = Cursor::new(&bytes);
659

            
660
36
    let reader = if let Some(ref content_type) = content_type {
661
28
        let format = image_format(content_type)?;
662
28
        image::io::Reader::with_format(cursor, format)
663
    } else {
664
4
        image::io::Reader::new(cursor)
665
            .with_guessed_format()
666
            .map_err(|_| LoadingError::Other(String::from("unknown image format")))?
667
    };
668

            
669
32
    let image = reader
670
        .decode()
671
4
        .map_err(|e| LoadingError::Other(format!("error decoding image: {e}")))?;
672

            
673
30
    let bytes = if load_options.keep_image_data {
674
        Some(bytes)
675
    } else {
676
30
        None
677
    };
678

            
679
30
    let surface = SharedImageSurface::from_image(&image, content_type.as_deref(), bytes)
680
30
        .map_err(|e| image_loading_error_from_cairo(e, aurl))?;
681

            
682
30
    Ok(Resource::Image(surface))
683
32
}
684

            
685
34
fn content_type_for_image(mime_type: &Mime) -> Option<String> {
686
    // See issue #548 - data: URLs without a MIME-type automatically
687
    // fall back to "text/plain;charset=US-ASCII".  Some (old?) versions of
688
    // Adobe Illustrator generate data: URLs without MIME-type for image
689
    // data.  We'll catch this and fall back to sniffing by unsetting the
690
    // content_type.
691
34
    let unspecified_mime_type = Mime::from_str("text/plain;charset=US-ASCII").unwrap();
692

            
693
63
    if *mime_type == unspecified_mime_type {
694
5
        None
695
    } else {
696
29
        Some(format!("{}/{}", mime_type.type_, mime_type.subtype))
697
    }
698
34
}
699

            
700
fn human_readable_url(aurl: &AllowedUrl) -> &str {
701
    if aurl.scheme() == "data" {
702
        // avoid printing a huge data: URL for image data
703
        "data URL"
704
    } else {
705
        aurl.as_ref()
706
    }
707
}
708

            
709
fn image_loading_error_from_cairo(status: cairo::Error, aurl: &AllowedUrl) -> LoadingError {
710
    let url = human_readable_url(aurl);
711

            
712
    match status {
713
        cairo::Error::NoMemory => LoadingError::OutOfMemory(format!("loading image: {url}")),
714
        cairo::Error::InvalidSize => LoadingError::Other(format!("image too big: {url}")),
715
        _ => LoadingError::Other(format!("cairo error: {status}")),
716
    }
717
}
718

            
719
pub struct AcquiredNode {
720
    stack: Option<Rc<RefCell<NodeStack>>>,
721
    node: Node,
722
}
723

            
724
impl Drop for AcquiredNode {
725
2000144
    fn drop(&mut self) {
726
2000144
        if let Some(ref stack) = self.stack {
727
1500408
            let mut stack = stack.borrow_mut();
728
1500408
            let last = stack.pop().unwrap();
729
1499558
            assert!(last == self.node);
730
1499558
        }
731
1998876
    }
732
}
733

            
734
impl AcquiredNode {
735
1499539
    pub fn get(&self) -> &Node {
736
        &self.node
737
1499539
    }
738
}
739

            
740
/// Detects circular references between nodes, and enforces referencing limits.
741
///
742
/// Consider this fragment of SVG:
743
///
744
/// ```xml
745
/// <pattern id="foo">
746
///   <rect width="1" height="1" fill="url(#foo)"/>
747
/// </pattern>
748
/// ```
749
///
750
/// The pattern has a child element that references the pattern itself.  This kind of circular
751
/// reference is invalid.  The `AcquiredNodes` struct is passed around
752
/// wherever it may be necessary to resolve references to nodes, or to access nodes
753
/// "elsewhere" in the DOM that is not the current subtree.
754
///
755
/// Also, such constructs that reference other elements can be maliciously arranged like
756
/// in the [billion laughs attack][lol], to cause huge amounts of CPU to be consumed through
757
/// creating an exponential number of references.  `AcquiredNodes` imposes a hard limit on
758
/// the number of references that can be resolved for typical, well-behaved SVG documents.
759
///
760
/// The [`Self::acquire()`] and [`Self::acquire_ref()`] methods return an [`AcquiredNode`], which
761
/// acts like a smart pointer for a [`Node`].  Once a node has been acquired, it cannot be
762
/// acquired again until its [`AcquiredNode`] is dropped.  In the example above, a graphic element
763
/// would acquire the `pattern`, which would then acquire its `rect` child, which then would fail
764
/// to re-acquired the `pattern` — thus signaling a circular reference.
765
///
766
/// Those methods return an [`AcquireError`] to signal circular references.  Also, they
767
/// can return [`AcquireError::MaxReferencesExceeded`] if the aforementioned referencing limit
768
/// is exceeded.
769
///
770
/// [lol]: https://bitbucket.org/tiran/defusedxml
771
pub struct AcquiredNodes<'i> {
772
    document: &'i Document,
773
    num_elements_acquired: usize,
774
    node_stack: Rc<RefCell<NodeStack>>,
775
}
776

            
777
impl<'i> AcquiredNodes<'i> {
778
1103
    pub fn new(document: &Document) -> AcquiredNodes<'_> {
779
1103
        AcquiredNodes {
780
            document,
781
            num_elements_acquired: 0,
782
1103
            node_stack: Rc::new(RefCell::new(NodeStack::new())),
783
        }
784
1103
    }
785

            
786
41
    pub fn lookup_image(&self, href: &str) -> Result<SharedImageSurface, LoadingError> {
787
41
        self.document.lookup_image(href)
788
41
    }
789

            
790
110
    pub fn lookup_resource(&self, url: &str) -> Result<Resource, LoadingError> {
791
110
        self.document.lookup_resource(url)
792
110
    }
793

            
794
    /// Acquires a node by its id.
795
    ///
796
    /// This is typically used during an "early resolution" stage, when XML `id`s are being
797
    /// resolved to node references.
798
1001035
    pub fn acquire(&mut self, node_id: &NodeId) -> Result<AcquiredNode, AcquireError> {
799
1001035
        self.num_elements_acquired += 1;
800

            
801
        // This is a mitigation for SVG files that try to instance a huge number of
802
        // elements via <use>, recursive patterns, etc.  See limits.rs for details.
803
1001035
        if self.num_elements_acquired > limits::MAX_REFERENCED_ELEMENTS {
804
13
            return Err(AcquireError::MaxReferencesExceeded);
805
        }
806

            
807
        // FIXME: callers shouldn't have to know that get_node() can initiate a file load.
808
        // Maybe we should have the following stages:
809
        //   - load main SVG XML
810
        //
811
        //   - load secondary resources: SVG XML and other files like images
812
        //
813
        //   - Now that all files are loaded, resolve URL references
814
2002044
        let node = self
815
            .document
816
            .lookup_node(node_id)
817
1001034
            .ok_or_else(|| AcquireError::LinkNotFound(node_id.clone()))?;
818

            
819
1001010
        if node.borrow_element().is_accessed_by_reference() {
820
500820
            self.acquire_ref(&node)
821
        } else {
822
500190
            Ok(AcquiredNode { stack: None, node })
823
        }
824
1001035
    }
825

            
826
    /// Acquires a node whose reference is already known.
827
    ///
828
    /// This is useful for cases where a node is initially referenced by its id with
829
    /// [`Self::acquire`] and kept around for later use.  During the later use, the node
830
    /// needs to be re-acquired with this method.  For example:
831
    ///
832
    /// * At an "early resolution" stage, `acquire()` a pattern by its id, and keep around its
833
    /// [`Node`] reference.
834
    ///
835
    /// * At the drawing stage, `acquire_ref()` the pattern node that we already had, so that
836
    /// its child elements that reference other paint servers will be able to detect circular
837
    /// references to the pattern.
838
1502028
    pub fn acquire_ref(&self, node: &Node) -> Result<AcquiredNode, AcquireError> {
839
3001025
        if self.node_stack.borrow().contains(node) {
840
11
            Err(AcquireError::CircularReference(node.clone()))
841
        } else {
842
1502017
            self.node_stack.borrow_mut().push(node);
843
1498997
            Ok(AcquiredNode {
844
1498997
                stack: Some(self.node_stack.clone()),
845
1498997
                node: node.clone(),
846
            })
847
        }
848
1499008
    }
849
}
850

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

            
858
impl NodeStack {
859
501344
    pub fn new() -> NodeStack {
860
501344
        NodeStack(Vec::new())
861
501344
    }
862

            
863
1500555
    pub fn push(&mut self, node: &Node) {
864
1500555
        self.0.push(node.clone());
865
1500555
    }
866

            
867
1499733
    pub fn pop(&mut self) -> Option<Node> {
868
1499733
        self.0.pop()
869
1499733
    }
870

            
871
1499638
    pub fn contains(&self, node: &Node) -> bool {
872
16782094
        self.0.iter().any(|n| *n == *node)
873
1499638
    }
874
}
875

            
876
/// Used to build a tree of SVG nodes while an XML document is being read.
877
///
878
/// This struct holds the document-related state while loading an SVG document from XML:
879
/// the loading options, the partially-built tree of nodes, the CSS stylesheets that
880
/// appear while loading the document.
881
///
882
/// The XML loader asks a `DocumentBuilder` to
883
/// [`append_element`][DocumentBuilder::append_element],
884
/// [`append_characters`][DocumentBuilder::append_characters], etc.  When all the XML has
885
/// been consumed, the caller can use [`build`][DocumentBuilder::build] to get a
886
/// fully-loaded [`Document`].
887
pub struct DocumentBuilder {
888
    /// Metadata for the document's lifetime.
889
    session: Session,
890

            
891
    /// Loading options; mainly the URL resolver.
892
    load_options: Arc<LoadOptions>,
893

            
894
    /// Root node of the tree.
895
    tree: Option<Node>,
896

            
897
    /// Mapping from `id` attributes to nodes.
898
    ids: HashMap<String, Node>,
899

            
900
    /// Stylesheets defined in the document.
901
    stylesheets: Vec<Stylesheet>,
902
}
903

            
904
impl DocumentBuilder {
905
1098
    pub fn new(session: Session, load_options: Arc<LoadOptions>) -> DocumentBuilder {
906
1098
        DocumentBuilder {
907
1098
            session,
908
1098
            load_options,
909
1098
            tree: None,
910
1098
            ids: HashMap::new(),
911
1098
            stylesheets: Vec::new(),
912
        }
913
1098
    }
914

            
915
    /// Adds a stylesheet in order to the document.
916
    ///
917
    /// Stylesheets will later be matched in the order in which they were added.
918
43
    pub fn append_stylesheet(&mut self, stylesheet: Stylesheet) {
919
43
        self.stylesheets.push(stylesheet);
920
43
    }
921

            
922
    /// Creates an element of the specified `name` as a child of `parent`.
923
    ///
924
    /// This is the main function to create new SVG elements while parsing XML.
925
    ///
926
    /// `name` is the XML element's name, for example `rect`.
927
    ///
928
    /// `attrs` has the XML element's attributes, e.g. cx/cy/r for `<circle cx="0" cy="0"
929
    /// r="5">`.
930
    ///
931
    /// If `parent` is `None` it means that we are creating the root node in the tree of
932
    /// elements.  The code will later validate that this is indeed an `<svg>` element.
933
1022286
    pub fn append_element(
934
        &mut self,
935
        name: &QualName,
936
        attrs: Attributes,
937
        parent: Option<Node>,
938
    ) -> Node {
939
1022286
        let node = Node::new(NodeData::new_element(&self.session, name, attrs));
940

            
941
1022286
        if let Some(id) = node.borrow_element().get_id() {
942
            // This is so we don't overwrite an existing id
943
11932
            self.ids
944
11934
                .entry(id.to_string())
945
23847
                .or_insert_with(|| node.clone());
946
1022283
        }
947

            
948
1023397
        if let Some(parent) = parent {
949
1021194
            parent.append(node.clone());
950
1022262
        } else if self.tree.is_none() {
951
1097
            self.tree = Some(node.clone());
952
        } else {
953
            panic!("The tree root has already been set");
954
        }
955

            
956
1022234
        node
957
1022234
    }
958

            
959
    /// Creates a node for an XML text element as a child of `parent`.
960
1034086
    pub fn append_characters(&mut self, text: &str, parent: &mut Node) {
961
2068101
        if !text.is_empty() {
962
            // When the last child is a Chars node we can coalesce
963
            // the text and avoid screwing up the Pango layouts
964
2057537
            if let Some(child) = parent.last_child().filter(|c| c.is_chars()) {
965
2530
                child.borrow_chars().append(text);
966
2530
            } else {
967
1031569
                parent.append(Node::new(NodeData::new_chars(text)));
968
1034015
            };
969
        }
970
1034028
    }
971

            
972
    /// Does the final validation on the `Document` being read, and returns it.
973
1102
    pub fn build(self) -> Result<Document, LoadingError> {
974
        let DocumentBuilder {
975
1102
            load_options,
976
1102
            session,
977
1102
            tree,
978
1102
            ids,
979
1102
            stylesheets,
980
            ..
981
1102
        } = self;
982

            
983
1102
        match tree {
984
1101
            Some(root) if root.is_element() => {
985
2195
                if is_element_of_type!(root, Svg) {
986
1095
                    let mut document = Document {
987
1097
                        tree: root,
988
1097
                        session: session.clone(),
989
1097
                        ids,
990
1097
                        resources: RefCell::new(Resources::new()),
991
1095
                        load_options,
992
1095
                        stylesheets,
993
                    };
994

            
995
1095
                    document.cascade(&[], &session);
996

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