rsvg/
io.rs

1//! Utilities to acquire streams and data from from URLs.
2
3use data_url::{DataUrl, mime::Mime};
4use gio::{
5    Cancellable, File as GFile, InputStream, MemoryInputStream,
6    prelude::{FileExt, FileExtManual},
7};
8use glib::{self, Bytes as GBytes, object::Cast};
9use std::fmt;
10
11use crate::url_resolver::AllowedUrl;
12
13pub enum IoError {
14    BadDataUrl,
15    Glib(glib::Error),
16}
17
18impl From<glib::Error> for IoError {
19    fn from(e: glib::Error) -> IoError {
20        IoError::Glib(e)
21    }
22}
23
24impl fmt::Display for IoError {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        match *self {
27            IoError::BadDataUrl => write!(f, "invalid data: URL"),
28            IoError::Glib(ref e) => e.fmt(f),
29        }
30    }
31}
32
33pub struct BinaryData {
34    pub data: Vec<u8>,
35    pub mime_type: Option<Mime>,
36}
37
38fn decode_data_uri(uri: &str) -> Result<BinaryData, IoError> {
39    let data_url = DataUrl::process(uri).map_err(|_| IoError::BadDataUrl)?;
40
41    let mime = data_url.mime_type();
42
43    // data_url::mime::Mime doesn't impl Clone, so do it by hand
44
45    let mime_type = Mime {
46        type_: mime.type_.clone(),
47        subtype: mime.subtype.clone(),
48        parameters: mime.parameters.clone(),
49    };
50
51    let (bytes, fragment_id) = data_url.decode_to_vec().map_err(|_| IoError::BadDataUrl)?;
52
53    // See issue #377 - per the data: URL spec
54    // (https://fetch.spec.whatwg.org/#data-urls), those URLs cannot
55    // have fragment identifiers.  So, just return an error if we find
56    // one.  This probably indicates mis-quoted SVG data inside the
57    // data: URL.
58    if fragment_id.is_some() {
59        return Err(IoError::BadDataUrl);
60    }
61
62    Ok(BinaryData {
63        data: bytes,
64        mime_type: Some(mime_type),
65    })
66}
67
68/// Creates a stream for reading.  The url can be a data: URL or a plain URI.
69pub fn acquire_stream(
70    aurl: &AllowedUrl,
71    cancellable: Option<&Cancellable>,
72) -> Result<InputStream, IoError> {
73    let uri = aurl.as_str();
74
75    if uri.starts_with("data:") {
76        let BinaryData { data, .. } = decode_data_uri(uri)?;
77
78        //        {
79        //            use std::fs::File;
80        //            use std::io::prelude::*;
81        //
82        //            let mut file = File::create("data.bin").unwrap();
83        //            file.write_all(&data).unwrap();
84        //        }
85
86        let stream = MemoryInputStream::from_bytes(&GBytes::from_owned(data));
87        Ok(stream.upcast::<InputStream>())
88    } else {
89        let file = GFile::for_uri(uri);
90        let stream = file.read(cancellable)?;
91
92        Ok(stream.upcast::<InputStream>())
93    }
94}
95
96/// Reads the entire contents pointed by an URL.  The url can be a data: URL or a plain URI.
97pub fn acquire_data(
98    aurl: &AllowedUrl,
99    cancellable: Option<&Cancellable>,
100) -> Result<BinaryData, IoError> {
101    let uri = aurl.as_str();
102
103    if uri.starts_with("data:") {
104        Ok(decode_data_uri(uri)?)
105    } else {
106        let file = GFile::for_uri(uri);
107        let (contents, _etag) = file.load_contents(cancellable)?;
108
109        Ok(BinaryData {
110            data: contents.to_vec(),
111            mime_type: None,
112        })
113    }
114}