rsvg/error.rs
1//! Error types.
2
3use std::error;
4use std::fmt;
5
6use cssparser::{BasicParseError, BasicParseErrorKind, ParseErrorKind, ToCss};
7use markup5ever::QualName;
8
9#[cfg(doc)]
10use crate::RenderingError;
11
12use crate::document::NodeId;
13use crate::io::IoError;
14use crate::limits;
15use crate::node::Node;
16
17/// A short-lived error.
18///
19/// The lifetime of the error is the same as the `cssparser::ParserInput` that
20/// was used to create a `cssparser::Parser`. That is, it is the lifetime of
21/// the string data that is being parsed.
22///
23/// The code flow will sometimes require preserving this error as a long-lived struct;
24/// see the `impl<'i, O> AttributeResultExt<O> for Result<O, ParseError<'i>>` for that
25/// purpose.
26pub type ParseError<'i> = cssparser::ParseError<'i, ValueErrorKind>;
27
28/// A simple error which refers to an attribute's value
29#[derive(Debug, Clone)]
30pub enum ValueErrorKind {
31 /// A property with the specified name was not found
32 UnknownProperty,
33
34 /// The value could not be parsed
35 Parse(String),
36
37 // The value could be parsed, but is invalid
38 Value(String),
39}
40
41impl ValueErrorKind {
42 pub fn parse_error(s: &str) -> ValueErrorKind {
43 ValueErrorKind::Parse(s.to_string())
44 }
45
46 pub fn value_error(s: &str) -> ValueErrorKind {
47 ValueErrorKind::Value(s.to_string())
48 }
49}
50
51impl fmt::Display for ValueErrorKind {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match *self {
54 ValueErrorKind::UnknownProperty => write!(f, "unknown property name"),
55
56 ValueErrorKind::Parse(ref s) => write!(f, "parse error: {s}"),
57
58 ValueErrorKind::Value(ref s) => write!(f, "invalid value: {s}"),
59 }
60 }
61}
62
63impl<'a> From<BasicParseError<'a>> for ValueErrorKind {
64 fn from(e: BasicParseError<'_>) -> ValueErrorKind {
65 let BasicParseError { kind, .. } = e;
66
67 let msg = match kind {
68 BasicParseErrorKind::UnexpectedToken(_) => "unexpected token",
69 BasicParseErrorKind::EndOfInput => "unexpected end of input",
70 BasicParseErrorKind::AtRuleInvalid(_) => "invalid @-rule",
71 BasicParseErrorKind::AtRuleBodyInvalid => "invalid @-rule body",
72 BasicParseErrorKind::QualifiedRuleInvalid => "invalid qualified rule",
73 };
74
75 ValueErrorKind::parse_error(msg)
76 }
77}
78
79/// A complete error for an attribute and its erroneous value
80#[derive(Debug, Clone)]
81pub struct ElementError {
82 pub attr: QualName,
83 pub err: ValueErrorKind,
84}
85
86impl fmt::Display for ElementError {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 write!(f, "{:?}: {}", self.attr.expanded(), self.err)
89 }
90}
91
92/// Errors returned when looking up a resource by URL reference.
93#[derive(Debug, Clone)]
94pub enum DefsLookupErrorKind {
95 /// Error when parsing the id to lookup.
96 InvalidId,
97
98 /// For internal use only.
99 ///
100 // FIXME: this is returned internally from Handle.lookup_node(), and gets translated
101 // to Ok(false). Don't expose this internal code in the public API.
102 NotFound,
103}
104
105impl fmt::Display for DefsLookupErrorKind {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 match *self {
108 DefsLookupErrorKind::InvalidId => write!(f, "invalid id"),
109 DefsLookupErrorKind::NotFound => write!(f, "not found"),
110 }
111 }
112}
113
114/// Errors that can happen while rendering or measuring an SVG document.
115///
116/// This is the internal version of [`crate::api::RenderingError`]; they are the same
117/// except that this one has an `InvalidTransform` variant which is only propagated
118/// internally. It is caught during the drawing process, and the element in question
119/// is simply not drawn, more or less per <https://www.w3.org/TR/css-transforms-1/#transform-function-lists>
120///
121/// "If a transform function causes the current transformation matrix of an
122/// object to be non-invertible, the object and its content do not get
123/// displayed."
124#[derive(Clone)]
125pub enum InternalRenderingError {
126 /// An error from the rendering backend.
127 Rendering(String),
128
129 /// A particular implementation-defined limit was exceeded.
130 LimitExceeded(ImplementationLimit),
131
132 /// A non-invertible transform was generated.
133 ///
134 /// This should not be a fatal error; we should catch it and just not render
135 /// the problematic element.
136 InvalidTransform,
137
138 CircularReference(Node),
139
140 /// Tried to reference an SVG element that does not exist.
141 IdNotFound,
142
143 /// Tried to reference an SVG element from a fragment identifier that is incorrect.
144 InvalidId(String),
145
146 /// Not enough memory was available for rendering.
147 OutOfMemory(String),
148
149 /// The rendering was interrupted via a [`gio::Cancellable`].
150 Cancelled,
151}
152
153impl From<DefsLookupErrorKind> for InternalRenderingError {
154 fn from(e: DefsLookupErrorKind) -> InternalRenderingError {
155 match e {
156 DefsLookupErrorKind::NotFound => InternalRenderingError::IdNotFound,
157 _ => InternalRenderingError::InvalidId(format!("{e}")),
158 }
159 }
160}
161
162impl From<InvalidTransform> for InternalRenderingError {
163 fn from(_: InvalidTransform) -> InternalRenderingError {
164 InternalRenderingError::InvalidTransform
165 }
166}
167
168impl fmt::Display for InternalRenderingError {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 match *self {
171 InternalRenderingError::Rendering(ref s) => write!(f, "rendering error: {s}"),
172 InternalRenderingError::LimitExceeded(ref l) => write!(f, "{l}"),
173 InternalRenderingError::InvalidTransform => write!(f, "invalid transform"),
174 InternalRenderingError::CircularReference(ref c) => {
175 write!(f, "circular reference in element {c}")
176 }
177 InternalRenderingError::IdNotFound => write!(f, "element id not found"),
178 InternalRenderingError::InvalidId(ref s) => write!(f, "invalid id: {s:?}"),
179 InternalRenderingError::OutOfMemory(ref s) => write!(f, "out of memory: {s}"),
180 InternalRenderingError::Cancelled => write!(f, "rendering cancelled"),
181 }
182 }
183}
184
185impl From<cairo::Error> for InternalRenderingError {
186 fn from(e: cairo::Error) -> InternalRenderingError {
187 InternalRenderingError::Rendering(format!("{e:?}"))
188 }
189}
190
191macro_rules! box_error {
192 ($from_ty:ty) => {
193 impl From<$from_ty> for Box<InternalRenderingError> {
194 fn from(e: $from_ty) -> Box<InternalRenderingError> {
195 Box::new(e.into())
196 }
197 }
198 };
199}
200
201box_error!(DefsLookupErrorKind);
202box_error!(InvalidTransform);
203box_error!(cairo::Error);
204
205/// Indicates that a transform is not invertible.
206///
207/// This generally represents an error from [`crate::transform::ValidTransform::try_from`], which is what we use
208/// to check affine transforms for validity.
209#[derive(Debug, PartialEq)]
210pub struct InvalidTransform;
211
212/// Errors from [`crate::document::AcquiredNodes`].
213pub enum AcquireError {
214 /// An element with the specified id was not found.
215 LinkNotFound(NodeId),
216
217 InvalidLinkType(NodeId),
218
219 /// A circular reference was detected; non-fatal error.
220 ///
221 /// Callers are expected to treat the offending element as invalid, for example
222 /// if a graphic element uses a pattern fill, but the pattern in turn includes
223 /// another graphic element that references the same pattern.
224 ///
225 /// ```xml
226 /// <pattern id="foo">
227 /// <rect width="1" height="1" fill="url(#foo)"/>
228 /// </pattern>
229 /// ```
230 CircularReference(Node),
231
232 /// Too many referenced objects were resolved; fatal error.
233 ///
234 /// Callers are expected to exit as early as possible and return an error to
235 /// the public API. See [`ImplementationLimit::TooManyReferencedElements`] for details.
236 MaxReferencesExceeded,
237}
238
239impl fmt::Display for AcquireError {
240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 match *self {
242 AcquireError::LinkNotFound(ref frag) => write!(f, "link not found: {frag}"),
243
244 AcquireError::InvalidLinkType(ref frag) => {
245 write!(f, "link \"{frag}\" is to object of invalid type")
246 }
247
248 AcquireError::CircularReference(ref node) => {
249 write!(f, "circular reference in node {node}")
250 }
251
252 AcquireError::MaxReferencesExceeded => {
253 write!(f, "maximum number of references exceeded")
254 }
255 }
256 }
257}
258
259/// Helper for converting `Result<O, E>` into `Result<O, ElementError>`
260///
261/// A `ElementError` requires a `QualName` that corresponds to the attribute to which the
262/// error refers, plus the actual `ValueErrorKind` that describes the error. However,
263/// parsing functions for attribute value types will want to return their own kind of
264/// error, instead of `ValueErrorKind`. If that particular error type has an `impl
265/// From<FooError> for ValueErrorKind`, then this trait helps assign attribute values in
266/// `set_atts()` methods as follows:
267///
268/// ```
269/// # use rsvg::doctest_only::AttributeResultExt;
270/// # use rsvg::doctest_only::ValueErrorKind;
271/// # use rsvg::doctest_only::ElementError;
272/// # use markup5ever::{QualName, Prefix, Namespace, LocalName};
273/// # type FooError = ValueErrorKind;
274/// fn parse_foo(value: &str) -> Result<(), FooError>
275/// # { Err(ValueErrorKind::value_error("test")) }
276///
277/// // It is assumed that there is an impl From<FooError> for ValueErrorKind
278/// # let attr = QualName::new(
279/// # Some(Prefix::from("")),
280/// # Namespace::from(""),
281/// # LocalName::from(""),
282/// # );
283/// let result = parse_foo("value").attribute(attr);
284/// assert!(result.is_err());
285/// # Ok::<(), ElementError>(())
286/// ```
287///
288/// The call to `.attribute(attr)` converts the `Result` from `parse_foo()` into a full
289/// `ElementError` with the provided `attr`.
290pub trait AttributeResultExt<O> {
291 fn attribute(self, attr: QualName) -> Result<O, ElementError>;
292}
293
294impl<O, E: Into<ValueErrorKind>> AttributeResultExt<O> for Result<O, E> {
295 fn attribute(self, attr: QualName) -> Result<O, ElementError> {
296 self.map_err(|e| e.into())
297 .map_err(|err| ElementError { attr, err })
298 }
299}
300
301/// Convert a short-lived ParseError into a long-lived ElementError
302///
303/// We extract this as a function, instead of putting it directly in the `map_err` invocation
304/// below in `impl<'i, O> AttributeResultExt<O> for Result<O, ParseError<'i>>`, because
305/// putting it there as a closure generates too many duplicated copies of the code. The generic
306/// parameter `O` in that `impl` is for the result `Ok()` value, not for the `Err`, after all.
307fn parse_error_to_element_error<'i>(e: ParseError<'i>, attr: QualName) -> ElementError {
308 // FIXME: eventually, here we'll want to preserve the location information
309
310 let ParseError {
311 kind,
312 location: _location,
313 } = e;
314
315 match kind {
316 ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(tok)) => {
317 let mut s = String::from("unexpected token '");
318 tok.to_css(&mut s).unwrap(); // FIXME: what do we do with a fmt::Error?
319 s.push('\'');
320
321 ElementError {
322 attr,
323 err: ValueErrorKind::Parse(s),
324 }
325 }
326
327 ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput) => ElementError {
328 attr,
329 err: ValueErrorKind::parse_error("unexpected end of input"),
330 },
331
332 ParseErrorKind::Basic(_) => {
333 unreachable!("attribute parsers should not return errors for CSS rules")
334 }
335
336 ParseErrorKind::Custom(err) => ElementError { attr, err },
337 }
338}
339
340/// Turns a short-lived `ParseError` into a long-lived `ElementError`
341impl<'i, O> AttributeResultExt<O> for Result<O, ParseError<'i>> {
342 fn attribute(self, attr: QualName) -> Result<O, ElementError> {
343 self.map_err(|e| parse_error_to_element_error(e, attr))
344 }
345}
346
347/// Errors returned when resolving an URL
348#[derive(Debug, Clone)]
349pub enum AllowedUrlError {
350 /// parsing error from `Url::parse()`
351 UrlParseError(url::ParseError),
352
353 /// A base file/uri was not set
354 BaseRequired,
355
356 /// Cannot reference a file with a different URI scheme from the base file
357 DifferentUriSchemes,
358
359 /// Some scheme we don't allow loading
360 DisallowedScheme,
361
362 /// The requested file is not in the same directory as the base file,
363 /// or in one directory below the base file.
364 NotSiblingOrChildOfBaseFile,
365
366 /// Loaded file:// URLs cannot have a query part, e.g. `file:///foo?blah`
367 NoQueriesAllowed,
368
369 /// URLs may not have fragment identifiers at this stage
370 NoFragmentIdentifierAllowed,
371
372 /// `file:` URLs may not have a hostname
373 NoHostAllowed,
374
375 /// Error when obtaining the file path that corresponds to the URL
376 InvalidPathInUrl,
377
378 /// Error when obtaining the file path that corresponds to the base URL
379 InvalidPathInBaseUrl,
380
381 /// The base file cannot be the root of the file system
382 BaseIsRoot,
383
384 /// Error when canonicalizing either the file path or the base file path
385 CanonicalizationError,
386}
387
388impl fmt::Display for AllowedUrlError {
389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390 use AllowedUrlError::*;
391 match *self {
392 UrlParseError(e) => write!(f, "URL parse error: {e}"),
393 BaseRequired => write!(f, "base required"),
394 DifferentUriSchemes => write!(f, "different URI schemes"),
395 DisallowedScheme => write!(f, "disallowed scheme"),
396 NotSiblingOrChildOfBaseFile => write!(f, "not sibling or child of base file"),
397 NoQueriesAllowed => write!(f, "no queries allowed"),
398 NoFragmentIdentifierAllowed => write!(f, "no fragment identifier allowed"),
399 NoHostAllowed => write!(f, "no hostnames allowed"),
400 InvalidPathInUrl => write!(f, "invalid path in file URL"),
401 InvalidPathInBaseUrl => write!(f, "invalid path in base URL"),
402 BaseIsRoot => write!(f, "base is root"),
403 CanonicalizationError => write!(f, "canonicalization error"),
404 }
405 }
406}
407
408/// Errors returned when creating a `NodeId` out of a string
409#[derive(Debug, Clone)]
410pub enum NodeIdError {
411 NodeIdRequired,
412}
413
414impl From<NodeIdError> for ValueErrorKind {
415 fn from(e: NodeIdError) -> ValueErrorKind {
416 match e {
417 NodeIdError::NodeIdRequired => {
418 ValueErrorKind::value_error("fragment identifier required")
419 }
420 }
421 }
422}
423
424/// Errors that can happen while loading an SVG document.
425///
426/// All of these codes are for unrecoverable errors that keep an SVG document from being
427/// fully loaded and parsed. Note that SVG is very lenient with respect to document
428/// structure and the syntax of CSS property values; most errors there will not lead to a
429/// `LoadingError`. To see those errors, you may want to set the `RSVG_LOG=1` environment
430/// variable.
431///
432/// I/O errors get reported in the `Glib` variant, since librsvg uses GIO internally for
433/// all input/output.
434#[non_exhaustive]
435#[derive(Debug, Clone)]
436pub enum LoadingError {
437 /// XML syntax error.
438 XmlParseError(String),
439
440 /// Not enough memory to load the document.
441 OutOfMemory(String),
442
443 /// A malformed or disallowed URL was used.
444 BadUrl,
445
446 /// An invalid stylesheet was used.
447 BadCss,
448
449 /// There is no `<svg>` root element in the XML.
450 NoSvgRoot,
451
452 /// I/O error.
453 Io(String),
454
455 /// A particular implementation-defined limit was exceeded.
456 LimitExceeded(ImplementationLimit),
457
458 /// Catch-all for loading errors.
459 Other(String),
460}
461
462/// Errors for implementation-defined limits, to mitigate malicious SVG documents.
463///
464/// These get emitted as [`LoadingError::LimitExceeded`] or [`RenderingError::LimitExceeded`].
465/// The limits are present to mitigate malicious SVG documents which may try to exhaust
466/// all available memory, or which would use large amounts of CPU time.
467#[non_exhaustive]
468#[derive(Debug, Copy, Clone)]
469pub enum ImplementationLimit {
470 /// Document exceeded the maximum number of times that elements
471 /// can be referenced through URL fragments.
472 ///
473 /// This is a mitigation for malicious documents that attempt to
474 /// consume exponential amounts of CPU time by creating millions
475 /// of references to SVG elements. For example, the `<use>` and
476 /// `<pattern>` elements allow referencing other elements, which
477 /// can in turn reference other elements. This can be used to
478 /// create documents which would require exponential amounts of
479 /// CPU time to be rendered.
480 ///
481 /// Librsvg deals with both cases by placing a limit on how many
482 /// references will be resolved during the SVG rendering process,
483 /// that is, how many `url(#foo)` will be resolved.
484 ///
485 /// These malicious documents are similar to the XML
486 /// [billion laughs attack], but done with SVG's referencing features.
487 ///
488 /// See issues
489 /// [#323](https://gitlab.gnome.org/GNOME/librsvg/issues/323) and
490 /// [#515](https://gitlab.gnome.org/GNOME/librsvg/issues/515) for
491 /// examples for the `<use>` and `<pattern>` elements,
492 /// respectively.
493 ///
494 /// [billion laughs attack]: https://bitbucket.org/tiran/defusedxml
495 TooManyReferencedElements,
496
497 /// Document exceeded the maximum number of elements that can be loaded.
498 ///
499 /// This is a mitigation for SVG files which create millions of
500 /// elements in an attempt to exhaust memory. Librsvg does not't
501 /// allow loading more than a certain number of elements during
502 /// the initial loading process.
503 TooManyLoadedElements,
504
505 /// Document exceeded the number of attributes that can be attached to
506 /// an element.
507 ///
508 /// This is here because librsvg uses u16 to address attributes. It should
509 /// be essentially impossible to actually hit this limit, because the
510 /// number of attributes that the SVG standard ascribes meaning to are
511 /// lower than this limit.
512 TooManyAttributes,
513
514 /// Document exceeded the maximum nesting level while rendering.
515 ///
516 /// Rendering is a recursive process, and there is a limit of how deep layers can
517 /// nest. This is to avoid malicious SVGs which try to have layers that are nested
518 /// extremely deep, as this could cause stack exhaustion.
519 MaximumLayerNestingDepthExceeded,
520}
521
522impl error::Error for LoadingError {}
523
524impl fmt::Display for LoadingError {
525 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
526 match *self {
527 LoadingError::XmlParseError(ref s) => write!(f, "XML parse error: {s}"),
528 LoadingError::OutOfMemory(ref s) => write!(f, "out of memory: {s}"),
529 LoadingError::BadUrl => write!(f, "invalid URL"),
530 LoadingError::BadCss => write!(f, "invalid CSS"),
531 LoadingError::NoSvgRoot => write!(f, "XML does not have <svg> root"),
532 LoadingError::Io(ref s) => write!(f, "I/O error: {s}"),
533 LoadingError::LimitExceeded(ref l) => write!(f, "{l}"),
534 LoadingError::Other(ref s) => write!(f, "{s}"),
535 }
536 }
537}
538
539impl From<glib::Error> for LoadingError {
540 fn from(e: glib::Error) -> LoadingError {
541 // FIXME: this is somewhat fishy; not all GError are I/O errors, but in librsvg
542 // most GError do come from gio. Some come from GdkPixbufLoader, though.
543 LoadingError::Io(format!("{e}"))
544 }
545}
546
547impl From<IoError> for LoadingError {
548 fn from(e: IoError) -> LoadingError {
549 match e {
550 IoError::BadDataUrl => LoadingError::BadUrl,
551 IoError::Glib(e) => LoadingError::Io(format!("{e}")),
552 }
553 }
554}
555
556impl fmt::Display for ImplementationLimit {
557 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
558 match *self {
559 ImplementationLimit::TooManyReferencedElements => write!(
560 f,
561 "exceeded more than {} referenced elements",
562 limits::MAX_REFERENCED_ELEMENTS
563 ),
564
565 ImplementationLimit::TooManyLoadedElements => write!(
566 f,
567 "cannot load more than {} XML elements",
568 limits::MAX_LOADED_ELEMENTS
569 ),
570
571 ImplementationLimit::TooManyAttributes => write!(
572 f,
573 "cannot load more than {} XML attributes",
574 limits::MAX_LOADED_ATTRIBUTES
575 ),
576
577 ImplementationLimit::MaximumLayerNestingDepthExceeded => write!(
578 f,
579 "maximum depth of {} nested layers has been exceeded",
580 limits::MAX_LAYER_NESTING_DEPTH,
581 ),
582 }
583 }
584}