1
//! Utilities to acquire streams and data from from URLs.
2

            
3
use data_url::{mime::Mime, DataUrl};
4
use gio::{
5
    prelude::{FileExt, FileExtManual},
6
    Cancellable, File as GFile, InputStream, MemoryInputStream,
7
};
8
use glib::{self, object::Cast, Bytes as GBytes};
9
use std::fmt;
10
use std::str::FromStr;
11

            
12
use crate::url_resolver::AllowedUrl;
13

            
14
pub enum IoError {
15
    BadDataUrl,
16
    Glib(glib::Error),
17
}
18

            
19
impl From<glib::Error> for IoError {
20
    fn from(e: glib::Error) -> IoError {
21
        IoError::Glib(e)
22
    }
23
}
24

            
25
impl fmt::Display for IoError {
26
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27
        match *self {
28
            IoError::BadDataUrl => write!(f, "invalid data: URL"),
29
            IoError::Glib(ref e) => e.fmt(f),
30
        }
31
    }
32
}
33

            
34
pub struct BinaryData {
35
    pub data: Vec<u8>,
36
    pub mime_type: Mime,
37
}
38

            
39
209
fn decode_data_uri(uri: &str) -> Result<BinaryData, IoError> {
40
209
    let data_url = DataUrl::process(uri).map_err(|_| IoError::BadDataUrl)?;
41

            
42
209
    let mime = data_url.mime_type();
43
209

            
44
209
    // data_url::mime::Mime doesn't impl Clone, so do it by hand
45
209

            
46
209
    let mime_type = Mime {
47
209
        type_: mime.type_.clone(),
48
209
        subtype: mime.subtype.clone(),
49
209
        parameters: mime.parameters.clone(),
50
209
    };
51

            
52
209
    let (bytes, fragment_id) = data_url.decode_to_vec().map_err(|_| IoError::BadDataUrl)?;
53

            
54
    // See issue #377 - per the data: URL spec
55
    // (https://fetch.spec.whatwg.org/#data-urls), those URLs cannot
56
    // have fragment identifiers.  So, just return an error if we find
57
    // one.  This probably indicates mis-quoted SVG data inside the
58
    // data: URL.
59
209
    if fragment_id.is_some() {
60
38
        return Err(IoError::BadDataUrl);
61
171
    }
62
171

            
63
171
    Ok(BinaryData {
64
171
        data: bytes,
65
171
        mime_type,
66
171
    })
67
209
}
68

            
69
/// Creates a stream for reading.  The url can be a data: URL or a plain URI.
70
836
pub fn acquire_stream(
71
836
    aurl: &AllowedUrl,
72
836
    cancellable: Option<&Cancellable>,
73
836
) -> Result<InputStream, IoError> {
74
836
    let uri = aurl.as_str();
75
836

            
76
836
    if uri.starts_with("data:") {
77
57
        let BinaryData { data, .. } = decode_data_uri(uri)?;
78

            
79
        //        {
80
        //            use std::fs::File;
81
        //            use std::io::prelude::*;
82
        //
83
        //            let mut file = File::create("data.bin").unwrap();
84
        //            file.write_all(&data).unwrap();
85
        //        }
86

            
87
19
        let stream = MemoryInputStream::from_bytes(&GBytes::from_owned(data));
88
19
        Ok(stream.upcast::<InputStream>())
89
    } else {
90
779
        let file = GFile::for_uri(uri);
91
779
        let stream = file.read(cancellable)?;
92

            
93
779
        Ok(stream.upcast::<InputStream>())
94
    }
95
836
}
96

            
97
/// Reads the entire contents pointed by an URL.  The url can be a data: URL or a plain URI.
98
836
pub fn acquire_data(
99
836
    aurl: &AllowedUrl,
100
836
    cancellable: Option<&Cancellable>,
101
836
) -> Result<BinaryData, IoError> {
102
836
    let uri = aurl.as_str();
103
836

            
104
836
    if uri.starts_with("data:") {
105
152
        Ok(decode_data_uri(uri)?)
106
    } else {
107
684
        let file = GFile::for_uri(uri);
108
684
        let (contents, _etag) = file.load_contents(cancellable)?;
109

            
110
684
        let (content_type, _uncertain) = gio::content_type_guess(Some(uri), &contents);
111

            
112
684
        let mime_type = if let Some(mime_type_str) = gio::content_type_get_mime_type(&content_type)
113
        {
114
684
            Mime::from_str(&mime_type_str)
115
684
                .expect("gio::content_type_get_mime_type returned an invalid MIME-type!?")
116
        } else {
117
            Mime::from_str("application/octet-stream").unwrap()
118
        };
119

            
120
684
        Ok(BinaryData {
121
684
            data: contents.to_vec(),
122
684
            mime_type,
123
684
        })
124
    }
125
836
}