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::{
22
    draw_tree, with_saved_cr, DrawingMode, RenderingConfiguration, SvgNesting,
23
};
24
use crate::error::{AcquireError, InternalRenderingError, LoadingError, NodeIdError};
25
use crate::io::{self, BinaryData};
26
use crate::is_element_of_type;
27
use crate::limits;
28
use crate::node::{CascadedValues, Node, NodeBorrow, NodeData};
29
use crate::rect::Rect;
30
use crate::rsvg_log;
31
use crate::session::Session;
32
use crate::structure::IntrinsicDimensions;
33
use crate::surface_utils::shared_surface::SharedImageSurface;
34
use crate::url_resolver::{AllowedUrl, UrlResolver};
35
use crate::xml::{xml_load_from_possibly_compressed_stream, Attributes};
36

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

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

            
54
2676
        match (url, id) {
55
2556
            (None, Some(id)) if !id.is_empty() => Ok(NodeId::Internal(String::from(id))),
56
71
            (Some(url), Some(id)) if !id.is_empty() => {
57
71
                Ok(NodeId::External(String::from(url), String::from(id)))
58
71
            }
59
51
            _ => Err(NodeIdError::NodeIdRequired),
60
        }
61
2678
    }
62
}
63

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

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

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

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

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

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

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

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

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

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

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

            
155
/// A loaded SVG file and its derived data.
156
pub struct Document {
157
    /// Tree of nodes; the root is guaranteed to be an `<svg>` element.
158
    tree: Node,
159

            
160
    /// Metadata about the SVG handle.
161
    session: Session,
162

            
163
    /// Mapping from `id` attributes to nodes.
164
    ids: HashMap<String, Node>,
165

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

            
173
    /// Used to load referenced resources.
174
    load_options: Arc<LoadOptions>,
175

            
176
    /// Stylesheets defined in the document.
177
    stylesheets: Vec<Stylesheet>,
178
}
179

            
180
impl Document {
181
    /// Constructs a `Document` by loading it from a stream.
182
1128
    pub fn load_from_stream(
183
        session: Session,
184
        load_options: Arc<LoadOptions>,
185
        stream: &gio::InputStream,
186
        cancellable: Option<&gio::Cancellable>,
187
    ) -> Result<Document, LoadingError> {
188
1128
        xml_load_from_possibly_compressed_stream(
189
1128
            session.clone(),
190
1128
            DocumentBuilder::new(session, load_options.clone()),
191
1128
            load_options,
192
            stream,
193
            cancellable,
194
1128
        )
195
1128
    }
196

            
197
    /// Utility function to load a document from a static string in tests.
198
    #[cfg(test)]
199
5
    pub fn load_from_bytes(input: &'static [u8]) -> Document {
200
5
        let bytes = glib::Bytes::from_static(input);
201
5
        let stream = gio::MemoryInputStream::from_bytes(&bytes);
202

            
203
5
        Document::load_from_stream(
204
5
            Session::new_for_test_suite(),
205
5
            Arc::new(LoadOptions::new(UrlResolver::new(None))),
206
5
            &stream.upcast(),
207
5
            None::<&gio::Cancellable>,
208
        )
209
        .unwrap()
210
5
    }
211

            
212
    /// Gets the root node.  This is guaranteed to be an `<svg>` element.
213
4610
    pub fn root(&self) -> Node {
214
4610
        self.tree.clone()
215
4610
    }
216

            
217
    /// Looks up a node in this document or one of its resources by its `id` attribute.
218
1001024
    fn lookup_node(
219
        &self,
220
        node_id: &NodeId,
221
        cancellable: Option<&gio::Cancellable>,
222
    ) -> Option<Node> {
223
1001024
        match node_id {
224
1000851
            NodeId::Internal(id) => self.lookup_internal_node(id),
225
346
            NodeId::External(url, id) => self
226
                .resources
227
                .borrow_mut()
228
173
                .lookup_node(&self.session, &self.load_options, url, id, cancellable)
229
173
                .ok(),
230
        }
231
1001024
    }
232

            
233
    /// Looks up a node in this document by its `id` attribute.
234
1000710
    pub fn lookup_internal_node(&self, id: &str) -> Option<Node> {
235
2001369
        self.ids.get(id).map(|n| (*n).clone())
236
1000710
    }
237

            
238
    /// Loads a resource by URL, or returns a pre-loaded one.
239
152
    fn lookup_resource(
240
        &self,
241
        url: &str,
242
        cancellable: Option<&gio::Cancellable>,
243
    ) -> Result<Resource, LoadingError> {
244
152
        let aurl = self
245
            .load_options
246
            .url_resolver
247
            .resolve_href(url)
248
2
            .map_err(|_| LoadingError::BadUrl)?;
249

            
250
302
        self.resources.borrow_mut().lookup_resource(
251
151
            &self.session,
252
151
            &self.load_options,
253
            &aurl,
254
            cancellable,
255
        )
256
152
    }
257

            
258
    /// Runs the CSS cascade on the document tree
259
    ///
260
    /// This uses the default UserAgent stylesheet, the document's internal stylesheets,
261
    /// plus an extra set of stylesheets supplied by the caller.
262
1115
    pub fn cascade(&mut self, extra: &[Stylesheet], session: &Session) {
263
        let stylesheets = {
264
            static UA_STYLESHEETS: OnceLock<Vec<Stylesheet>> = OnceLock::new();
265
1227
            UA_STYLESHEETS.get_or_init(|| {
266
224
                vec![Stylesheet::from_data(
267
                    include_str!("ua.css"),
268
112
                    &UrlResolver::new(None),
269
112
                    Origin::UserAgent,
270
112
                    Session::default(),
271
                )
272
                .expect("could not parse user agent stylesheet for librsvg, there's a bug!")]
273
112
            })
274
        };
275
1115
        css::cascade(
276
1115
            &mut self.tree,
277
1115
            stylesheets,
278
1115
            &self.stylesheets,
279
            extra,
280
            session,
281
        );
282
1115
    }
283

            
284
1162
    pub fn get_intrinsic_dimensions(&self) -> IntrinsicDimensions {
285
1162
        let root = self.root();
286
1162
        let cascaded = CascadedValues::new_from_node(&root);
287
1162
        let values = cascaded.get();
288
2324
        borrow_element_as!(self.root(), Svg).get_intrinsic_dimensions(values)
289
1162
    }
290

            
291
1005
    pub fn render_document(
292
        &self,
293
        session: &Session,
294
        cr: &cairo::Context,
295
        viewport: &cairo::Rectangle,
296
        options: &RenderingOptions,
297
    ) -> Result<(), InternalRenderingError> {
298
1005
        let root = self.root();
299
1005
        self.render_layer(session, cr, root, viewport, options)
300
1005
    }
301

            
302
1017
    pub fn render_layer(
303
        &self,
304
        session: &Session,
305
        cr: &cairo::Context,
306
        node: Node,
307
        viewport: &cairo::Rectangle,
308
        options: &RenderingOptions,
309
    ) -> Result<(), InternalRenderingError> {
310
1017
        cr.status()?;
311

            
312
1017
        let root = self.root();
313

            
314
1017
        let viewport = Rect::from(*viewport);
315

            
316
1018
        let config = options.to_rendering_configuration(false);
317

            
318
2034
        with_saved_cr(cr, || {
319
2034
            draw_tree(
320
1017
                session.clone(),
321
1017
                DrawingMode::LimitToStack { node, root },
322
1017
                cr,
323
1017
                viewport,
324
1017
                config,
325
1017
                &mut AcquiredNodes::new(self, options.cancellable.clone()),
326
            )
327
1011
            .map(|_bbox| ())
328
1017
        })
329
1017
    }
330

            
331
84
    fn geometry_for_layer(
332
        &self,
333
        session: &Session,
334
        node: Node,
335
        viewport: Rect,
336
        options: &RenderingOptions,
337
    ) -> Result<(Rect, Rect), InternalRenderingError> {
338
84
        let root = self.root();
339

            
340
84
        let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
341
84
        let cr = cairo::Context::new(&target)?;
342

            
343
85
        let config = options.to_rendering_configuration(true);
344

            
345
83
        let bbox = draw_tree(
346
85
            session.clone(),
347
85
            DrawingMode::LimitToStack { node, root },
348
            &cr,
349
            viewport,
350
85
            config,
351
85
            &mut AcquiredNodes::new(self, options.cancellable.clone()),
352
83
        )?;
353

            
354
83
        let ink_rect = bbox.ink_rect.unwrap_or_default();
355
83
        let logical_rect = bbox.rect.unwrap_or_default();
356

            
357
85
        Ok((ink_rect, logical_rect))
358
83
    }
359

            
360
84
    pub fn get_geometry_for_layer(
361
        &self,
362
        session: &Session,
363
        node: Node,
364
        viewport: &cairo::Rectangle,
365
        options: &RenderingOptions,
366
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), InternalRenderingError> {
367
84
        let viewport = Rect::from(*viewport);
368

            
369
84
        let (ink_rect, logical_rect) = self.geometry_for_layer(session, node, viewport, options)?;
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
        options: &RenderingOptions,
382
    ) -> Result<BoundingBox, InternalRenderingError> {
383
12
        let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
384
12
        let cr = cairo::Context::new(&target)?;
385

            
386
12
        let node = node.clone();
387

            
388
12
        let config = options.to_rendering_configuration(true);
389

            
390
12
        draw_tree(
391
12
            session.clone(),
392
12
            DrawingMode::OnlyNode(node),
393
            &cr,
394
12
            unit_rectangle(),
395
12
            config,
396
12
            &mut AcquiredNodes::new(self, options.cancellable.clone()),
397
        )
398
12
    }
399

            
400
    /// Returns (ink_rect, logical_rect)
401
6
    pub fn get_geometry_for_element(
402
        &self,
403
        session: &Session,
404
        node: Node,
405
        options: &RenderingOptions,
406
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), InternalRenderingError> {
407
6
        let bbox = self.get_bbox_for_element(session, &node, options)?;
408

            
409
6
        let ink_rect = bbox.ink_rect.unwrap_or_default();
410
6
        let logical_rect = bbox.rect.unwrap_or_default();
411

            
412
        // Translate so ink_rect is always at offset (0, 0)
413
6
        let ofs = (-ink_rect.x0, -ink_rect.y0);
414

            
415
6
        Ok((
416
6
            cairo::Rectangle::from(ink_rect.translate(ofs)),
417
6
            cairo::Rectangle::from(logical_rect.translate(ofs)),
418
        ))
419
6
    }
420

            
421
6
    pub fn render_element(
422
        &self,
423
        session: &Session,
424
        cr: &cairo::Context,
425
        node: Node,
426
        element_viewport: &cairo::Rectangle,
427
        options: &RenderingOptions,
428
    ) -> Result<(), InternalRenderingError> {
429
6
        cr.status()?;
430

            
431
6
        let bbox = self.get_bbox_for_element(session, &node, options)?;
432

            
433
6
        if bbox.ink_rect.is_none() || bbox.rect.is_none() {
434
            // Nothing to draw
435
            return Ok(());
436
        }
437

            
438
6
        let ink_r = bbox.ink_rect.unwrap_or_default();
439

            
440
6
        if ink_r.is_empty() {
441
            return Ok(());
442
        }
443

            
444
        // Render, transforming so element is at the new viewport's origin
445

            
446
12
        with_saved_cr(cr, || {
447
12
            let factor = (element_viewport.width() / ink_r.width())
448
6
                .min(element_viewport.height() / ink_r.height());
449

            
450
6
            cr.translate(element_viewport.x(), element_viewport.y());
451
6
            cr.scale(factor, factor);
452
6
            cr.translate(-ink_r.x0, -ink_r.y0);
453

            
454
6
            let config = options.to_rendering_configuration(false);
455

            
456
6
            draw_tree(
457
6
                session.clone(),
458
6
                DrawingMode::OnlyNode(node),
459
6
                cr,
460
6
                unit_rectangle(),
461
6
                config,
462
6
                &mut AcquiredNodes::new(self, options.cancellable.clone()),
463
            )
464
6
            .map(|_bbox| ())
465
6
        })
466
6
    }
467
}
468

            
469
18
fn unit_rectangle() -> Rect {
470
18
    Rect::from_size(1.0, 1.0)
471
18
}
472

            
473
/// Any kind of resource loaded while processing an SVG document: images, or SVGs themselves.
474
157
#[derive(Clone)]
475
pub enum Resource {
476
22
    Document(Rc<Document>),
477
135
    Image(SharedImageSurface),
478
}
479

            
480
struct Resources {
481
    resources: HashMap<AllowedUrl, Result<Resource, LoadingError>>,
482
}
483

            
484
impl Resources {
485
1111
    fn new() -> Resources {
486
1111
        Resources {
487
1111
            resources: Default::default(),
488
        }
489
1111
    }
490

            
491
173
    fn lookup_node(
492
        &mut self,
493
        session: &Session,
494
        load_options: &LoadOptions,
495
        url: &str,
496
        id: &str,
497
        cancellable: Option<&gio::Cancellable>,
498
    ) -> Result<Node, LoadingError> {
499
173
        self.get_extern_document(session, load_options, url, cancellable)
500
7
            .and_then(|resource| match resource {
501
7
                Resource::Document(doc) => doc.lookup_internal_node(id).ok_or(LoadingError::BadUrl),
502
                _ => unreachable!("get_extern_document() should already have ensured the document"),
503
7
            })
504
173
    }
505

            
506
173
    fn get_extern_document(
507
        &mut self,
508
        session: &Session,
509
        load_options: &LoadOptions,
510
        href: &str,
511
        cancellable: Option<&gio::Cancellable>,
512
    ) -> Result<Resource, LoadingError> {
513
173
        let aurl = load_options
514
            .url_resolver
515
            .resolve_href(href)
516
302
            .map_err(|_| LoadingError::BadUrl)?;
517

            
518
22
        let resource = self.lookup_resource(session, load_options, &aurl, cancellable)?;
519

            
520
7
        match resource {
521
7
            Resource::Document(_) => Ok(resource),
522
            _ => Err(LoadingError::Other(format!(
523
                "{href} is not an SVG document"
524
            ))),
525
        }
526
173
    }
527

            
528
173
    fn lookup_resource(
529
        &mut self,
530
        session: &Session,
531
        load_options: &LoadOptions,
532
        aurl: &AllowedUrl,
533
        cancellable: Option<&gio::Cancellable>,
534
    ) -> Result<Resource, LoadingError> {
535
173
        match self.resources.entry(aurl.clone()) {
536
134
            Entry::Occupied(e) => e.get().clone(),
537

            
538
39
            Entry::Vacant(e) => {
539
39
                let resource_result = load_resource(session, load_options, aurl, cancellable);
540
39
                e.insert(resource_result.clone());
541
39
                resource_result
542
39
            }
543
        }
544
173
    }
545
}
546

            
547
39
fn load_resource(
548
    session: &Session,
549
    load_options: &LoadOptions,
550
    aurl: &AllowedUrl,
551
    cancellable: Option<&gio::Cancellable>,
552
) -> Result<Resource, LoadingError> {
553
39
    let data = io::acquire_data(aurl, cancellable)?;
554

            
555
39
    let svg_mime_type = Mime::from_str("image/svg+xml").unwrap();
556

            
557
39
    if data.mime_type == svg_mime_type {
558
6
        load_svg_resource_from_bytes(session, load_options, aurl, data, cancellable)
559
    } else {
560
33
        load_image_resource_from_bytes(load_options, aurl, data)
561
    }
562
39
}
563

            
564
6
fn load_svg_resource_from_bytes(
565
    session: &Session,
566
    load_options: &LoadOptions,
567
    aurl: &AllowedUrl,
568
    data: BinaryData,
569
    cancellable: Option<&gio::Cancellable>,
570
) -> Result<Resource, LoadingError> {
571
    let BinaryData {
572
6
        data: input_bytes,
573
6
        mime_type: _mime_type,
574
6
    } = data;
575

            
576
6
    let bytes = glib::Bytes::from_owned(input_bytes);
577
6
    let stream = gio::MemoryInputStream::from_bytes(&bytes);
578

            
579
6
    let document = Document::load_from_stream(
580
6
        session.clone(),
581
6
        Arc::new(load_options.copy_with_base_url(aurl)),
582
6
        &stream.upcast(),
583
        cancellable,
584
6
    )?;
585

            
586
6
    Ok(Resource::Document(Rc::new(document)))
587
6
}
588

            
589
33
fn load_image_resource_from_bytes(
590
    load_options: &LoadOptions,
591
    aurl: &AllowedUrl,
592
    data: BinaryData,
593
) -> Result<Resource, LoadingError> {
594
    let BinaryData {
595
33
        data: bytes,
596
33
        mime_type,
597
33
    } = data;
598

            
599
33
    if bytes.is_empty() {
600
        return Err(LoadingError::Other(String::from("no image data")));
601
    }
602

            
603
33
    let content_type = content_type_for_image(&mime_type);
604

            
605
33
    load_image_with_image_rs(aurl, bytes, content_type, load_options)
606
33
}
607

            
608
28
fn image_format(content_type: &str) -> Result<image::ImageFormat, LoadingError> {
609
    match content_type {
610
28
        "image/png" => Ok(image::ImageFormat::Png),
611
6
        "image/jpeg" => Ok(image::ImageFormat::Jpeg),
612
        "image/gif" => Ok(image::ImageFormat::Gif),
613
        "image/webp" => Ok(image::ImageFormat::WebP),
614

            
615
        #[cfg(feature = "avif")]
616
        "image/avif" => Ok(image::ImageFormat::Avif),
617

            
618
        _ => Err(LoadingError::Other(format!(
619
            "unsupported image format {content_type}"
620
        ))),
621
    }
622
28
}
623

            
624
33
fn load_image_with_image_rs(
625
    aurl: &AllowedUrl,
626
    bytes: Vec<u8>,
627
    content_type: Option<String>,
628
    load_options: &LoadOptions,
629
) -> Result<Resource, LoadingError> {
630
33
    let cursor = Cursor::new(&bytes);
631

            
632
38
    let reader = if let Some(ref content_type) = content_type {
633
28
        let format = image_format(content_type)?;
634
28
        image::ImageReader::with_format(cursor, format)
635
    } else {
636
5
        image::ImageReader::new(cursor)
637
            .with_guessed_format()
638
            .map_err(|_| LoadingError::Other(String::from("unknown image format")))?
639
    };
640

            
641
33
    let image = reader
642
        .decode()
643
6
        .map_err(|e| LoadingError::Other(format!("error decoding image: {e}")))?;
644

            
645
30
    let bytes = if load_options.keep_image_data {
646
        Some(bytes)
647
    } else {
648
30
        None
649
    };
650

            
651
30
    let surface = SharedImageSurface::from_image(&image, content_type.as_deref(), bytes)
652
        .map_err(|e| image_loading_error_from_cairo(e, aurl))?;
653

            
654
30
    Ok(Resource::Image(surface))
655
33
}
656

            
657
35
fn content_type_for_image(mime_type: &Mime) -> Option<String> {
658
    // See issue #548 - data: URLs without a MIME-type automatically
659
    // fall back to "text/plain;charset=US-ASCII".  Some (old?) versions of
660
    // Adobe Illustrator generate data: URLs without MIME-type for image
661
    // data.  We'll catch this and fall back to sniffing by unsetting the
662
    // content_type.
663
35
    let unspecified_mime_type = Mime::from_str("text/plain;charset=US-ASCII").unwrap();
664

            
665
64
    if *mime_type == unspecified_mime_type {
666
6
        None
667
    } else {
668
29
        Some(format!("{}/{}", mime_type.type_, mime_type.subtype))
669
    }
670
35
}
671

            
672
fn human_readable_url(aurl: &AllowedUrl) -> &str {
673
    if aurl.scheme() == "data" {
674
        // avoid printing a huge data: URL for image data
675
        "data URL"
676
    } else {
677
        aurl.as_ref()
678
    }
679
}
680

            
681
fn image_loading_error_from_cairo(status: cairo::Error, aurl: &AllowedUrl) -> LoadingError {
682
    let url = human_readable_url(aurl);
683

            
684
    match status {
685
        cairo::Error::NoMemory => LoadingError::OutOfMemory(format!("loading image: {url}")),
686
        cairo::Error::InvalidSize => LoadingError::Other(format!("image too big: {url}")),
687
        _ => LoadingError::Other(format!("cairo error: {status}")),
688
    }
689
}
690

            
691
pub struct AcquiredNode {
692
    stack: Option<Rc<RefCell<NodeStack>>>,
693
    node: Node,
694
}
695

            
696
impl Drop for AcquiredNode {
697
1999320
    fn drop(&mut self) {
698
1999320
        if let Some(ref stack) = self.stack {
699
1502095
            let mut stack = stack.borrow_mut();
700
1502095
            let last = stack.pop().unwrap();
701
1501172
            assert!(last == self.node);
702
1501172
        }
703
1997698
    }
704
}
705

            
706
impl AcquiredNode {
707
1500062
    pub fn get(&self) -> &Node {
708
        &self.node
709
1500062
    }
710
}
711

            
712
/// Detects circular references between nodes, and enforces referencing limits.
713
///
714
/// Consider this fragment of SVG:
715
///
716
/// ```xml
717
/// <pattern id="foo">
718
///   <rect width="1" height="1" fill="url(#foo)"/>
719
/// </pattern>
720
/// ```
721
///
722
/// The pattern has a child element that references the pattern itself.  This kind of circular
723
/// reference is invalid.  The `AcquiredNodes` struct is passed around
724
/// wherever it may be necessary to resolve references to nodes, or to access nodes
725
/// "elsewhere" in the DOM that is not the current subtree.
726
///
727
/// Also, such constructs that reference other elements can be maliciously arranged like
728
/// in the [billion laughs attack][lol], to cause huge amounts of CPU to be consumed through
729
/// creating an exponential number of references.  `AcquiredNodes` imposes a hard limit on
730
/// the number of references that can be resolved for typical, well-behaved SVG documents.
731
///
732
/// The [`Self::acquire()`] and [`Self::acquire_ref()`] methods return an [`AcquiredNode`], which
733
/// acts like a smart pointer for a [`Node`].  Once a node has been acquired, it cannot be
734
/// acquired again until its [`AcquiredNode`] is dropped.  In the example above, a graphic element
735
/// would acquire the `pattern`, which would then acquire its `rect` child, which then would fail
736
/// to re-acquired the `pattern` — thus signaling a circular reference.
737
///
738
/// Those methods return an [`AcquireError`] to signal circular references.  Also, they
739
/// can return [`AcquireError::MaxReferencesExceeded`] if the aforementioned referencing limit
740
/// is exceeded.
741
///
742
/// [lol]: https://bitbucket.org/tiran/defusedxml
743
pub struct AcquiredNodes<'i> {
744
    document: &'i Document,
745
    num_elements_acquired: usize,
746
    node_stack: Rc<RefCell<NodeStack>>,
747
    nodes_with_cycles: Vec<Node>,
748
    cancellable: Option<gio::Cancellable>,
749
}
750

            
751
impl<'i> AcquiredNodes<'i> {
752
1120
    pub fn new(document: &Document, cancellable: Option<gio::Cancellable>) -> AcquiredNodes<'_> {
753
1120
        AcquiredNodes {
754
            document,
755
            num_elements_acquired: 0,
756
1120
            node_stack: Rc::new(RefCell::new(NodeStack::new())),
757
1120
            nodes_with_cycles: Vec::new(),
758
1120
            cancellable,
759
        }
760
1120
    }
761

            
762
152
    pub fn lookup_resource(&self, url: &str) -> Result<Resource, LoadingError> {
763
304
        self.document
764
152
            .lookup_resource(url, self.cancellable.as_ref())
765
152
    }
766

            
767
    /// Acquires a node by its id.
768
    ///
769
    /// This is typically used during an "early resolution" stage, when XML `id`s are being
770
    /// resolved to node references.
771
1001702
    pub fn acquire(&mut self, node_id: &NodeId) -> Result<AcquiredNode, AcquireError> {
772
1001702
        self.num_elements_acquired += 1;
773

            
774
        // This is a mitigation for SVG files that try to instance a huge number of
775
        // elements via <use>, recursive patterns, etc.  See limits.rs for details.
776
1001702
        if self.num_elements_acquired > limits::MAX_REFERENCED_ELEMENTS {
777
13
            return Err(AcquireError::MaxReferencesExceeded);
778
        }
779

            
780
        // FIXME: callers shouldn't have to know that get_node() can initiate a file load.
781
        // Maybe we should have the following stages:
782
        //   - load main SVG XML
783
        //
784
        //   - load secondary resources: SVG XML and other files like images
785
        //
786
        //   - Now that all files are loaded, resolve URL references
787
2003378
        let node = self
788
            .document
789
1001689
            .lookup_node(node_id, self.cancellable.as_ref())
790
346
            .ok_or_else(|| AcquireError::LinkNotFound(node_id.clone()))?;
791

            
792
1001516
        if self.nodes_with_cycles.contains(&node) {
793
16
            return Err(AcquireError::CircularReference(node.clone()));
794
        }
795

            
796
1000553
        if node.borrow_element().is_accessed_by_reference() {
797
500822
            self.acquire_ref(&node)
798
        } else {
799
500253
            Ok(AcquiredNode { stack: None, node })
800
        }
801
1001276
    }
802

            
803
    /// Acquires a node whose reference is already known.
804
    ///
805
    /// This is useful for cases where a node is initially referenced by its id with
806
    /// [`Self::acquire`] and kept around for later use.  During the later use, the node
807
    /// needs to be re-acquired with this method.  For example:
808
    ///
809
    /// * At an "early resolution" stage, `acquire()` a pattern by its id, and keep around its
810
    ///   [`Node`] reference.
811
    ///
812
    /// * At the drawing stage, `acquire_ref()` the pattern node that we already had, so that
813
    ///   its child elements that reference other paint servers will be able to detect circular
814
    ///   references to the pattern.
815
1500703
    pub fn acquire_ref(&mut self, node: &Node) -> Result<AcquiredNode, AcquireError> {
816
1500703
        if self.nodes_with_cycles.contains(node) {
817
121
            Err(AcquireError::CircularReference(node.clone()))
818
3001098
        } else if self.node_stack.borrow().contains(node) {
819
24
            self.nodes_with_cycles.push(node.clone());
820
24
            Err(AcquireError::CircularReference(node.clone()))
821
        } else {
822
1500558
            self.node_stack.borrow_mut().push(node);
823
1500516
            Ok(AcquiredNode {
824
1500516
                stack: Some(self.node_stack.clone()),
825
1500516
                node: node.clone(),
826
            })
827
        }
828
1500661
    }
829
}
830

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

            
838
impl NodeStack {
839
501357
    pub fn new() -> NodeStack {
840
501357
        NodeStack(Vec::new())
841
501357
    }
842

            
843
1500580
    pub fn push(&mut self, node: &Node) {
844
1500580
        self.0.push(node.clone());
845
1500580
    }
846

            
847
1499184
    pub fn pop(&mut self) -> Option<Node> {
848
1499184
        self.0.pop()
849
1499184
    }
850

            
851
1500556
    pub fn contains(&self, node: &Node) -> bool {
852
16784662
        self.0.iter().any(|n| *n == *node)
853
1500556
    }
854
}
855

            
856
/// Used to build a tree of SVG nodes while an XML document is being read.
857
///
858
/// This struct holds the document-related state while loading an SVG document from XML:
859
/// the loading options, the partially-built tree of nodes, the CSS stylesheets that
860
/// appear while loading the document.
861
///
862
/// The XML loader asks a `DocumentBuilder` to
863
/// [`append_element`][DocumentBuilder::append_element],
864
/// [`append_characters`][DocumentBuilder::append_characters], etc.  When all the XML has
865
/// been consumed, the caller can use [`build`][DocumentBuilder::build] to get a
866
/// fully-loaded [`Document`].
867
pub struct DocumentBuilder {
868
    /// Metadata for the document's lifetime.
869
    session: Session,
870

            
871
    /// Loading options; mainly the URL resolver.
872
    load_options: Arc<LoadOptions>,
873

            
874
    /// Root node of the tree.
875
    tree: Option<Node>,
876

            
877
    /// Mapping from `id` attributes to nodes.
878
    ids: HashMap<String, Node>,
879

            
880
    /// Stylesheets defined in the document.
881
    stylesheets: Vec<Stylesheet>,
882
}
883

            
884
impl DocumentBuilder {
885
1096
    pub fn new(session: Session, load_options: Arc<LoadOptions>) -> DocumentBuilder {
886
1096
        DocumentBuilder {
887
1096
            session,
888
1096
            load_options,
889
1096
            tree: None,
890
1096
            ids: HashMap::new(),
891
1096
            stylesheets: Vec::new(),
892
        }
893
1096
    }
894

            
895
    /// Adds a stylesheet in order to the document.
896
    ///
897
    /// Stylesheets will later be matched in the order in which they were added.
898
43
    pub fn append_stylesheet(&mut self, stylesheet: Stylesheet) {
899
43
        self.stylesheets.push(stylesheet);
900
43
    }
901

            
902
    /// Creates an element of the specified `name` as a child of `parent`.
903
    ///
904
    /// This is the main function to create new SVG elements while parsing XML.
905
    ///
906
    /// `name` is the XML element's name, for example `rect`.
907
    ///
908
    /// `attrs` has the XML element's attributes, e.g. cx/cy/r for `<circle cx="0" cy="0"
909
    /// r="5">`.
910
    ///
911
    /// If `parent` is `None` it means that we are creating the root node in the tree of
912
    /// elements.  The code will later validate that this is indeed an `<svg>` element.
913
1024734
    pub fn append_element(
914
        &mut self,
915
        name: &QualName,
916
        attrs: Attributes,
917
        parent: Option<Node>,
918
    ) -> Node {
919
1024734
        let node = Node::new(NodeData::new_element(&self.session, name, attrs));
920

            
921
1024734
        if let Some(id) = node.borrow_element().get_id() {
922
11924
            match self.ids.entry(id.to_string()) {
923
                Entry::Occupied(_) => {
924
28
                    rsvg_log!(self.session, "ignoring duplicate id {id} for {node}");
925
                }
926

            
927
11913
                Entry::Vacant(e) => {
928
11913
                    e.insert(node.clone());
929
                }
930
            }
931
1024721
        }
932

            
933
1025886
        if let Some(parent) = parent {
934
1023664
            parent.append(node.clone());
935
1024680
        } else if self.tree.is_none() {
936
1112
            self.tree = Some(node.clone());
937
        } else {
938
            panic!("The tree root has already been set");
939
        }
940

            
941
1024578
        node
942
1024578
    }
943

            
944
    /// Creates a node for an XML text element as a child of `parent`.
945
1036599
    pub fn append_characters(&mut self, text: &str, parent: &mut Node) {
946
2072953
        if !text.is_empty() {
947
            // When the last child is a Chars node we can coalesce
948
            // the text and avoid screwing up the Pango layouts
949
2061224
            if let Some(child) = parent.last_child().filter(|c| c.is_chars()) {
950
2531
                child.borrow_chars().append(text);
951
2531
            } else {
952
1034041
                parent.append(Node::new(NodeData::new_chars(text)));
953
1036354
            };
954
        }
955
1036381
    }
956

            
957
    /// Does the final validation on the `Document` being read, and returns it.
958
1116
    pub fn build(self) -> Result<Document, LoadingError> {
959
        let DocumentBuilder {
960
1116
            load_options,
961
1116
            session,
962
1116
            tree,
963
1116
            ids,
964
1116
            stylesheets,
965
            ..
966
1116
        } = self;
967

            
968
1116
        match tree {
969
1115
            Some(root) if root.is_element() => {
970
1113
                if is_element_of_type!(root, Svg) {
971
1110
                    let mut document = Document {
972
1112
                        tree: root,
973
1112
                        session: session.clone(),
974
1112
                        ids,
975
1112
                        resources: RefCell::new(Resources::new()),
976
1110
                        load_options,
977
1110
                        stylesheets,
978
                    };
979

            
980
1110
                    document.cascade(&[], &session);
981

            
982
1110
                    Ok(document)
983
                } else {
984
3
                    Err(LoadingError::NoSvgRoot)
985
                }
986
1113
            }
987
            _ => Err(LoadingError::NoSvgRoot),
988
        }
989
1114
    }
990
}
991

            
992
#[cfg(test)]
993
mod tests {
994
    use super::*;
995

            
996
    #[test]
997
2
    fn parses_node_id() {
998
2
        assert_eq!(
999
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
    }
}