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

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

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

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

            
52
10
    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
10
    if fragment_id.is_some() {
60
2
        return Err(IoError::BadDataUrl);
61
    }
62

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

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

            
76
45
    if uri.starts_with("data:") {
77
3
        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
1
        let stream = MemoryInputStream::from_bytes(&GBytes::from_owned(data));
88
1
        Ok(stream.upcast::<InputStream>())
89
1
    } else {
90
41
        let file = GFile::for_uri(uri);
91
41
        let stream = file.read(cancellable)?;
92

            
93
41
        Ok(stream.upcast::<InputStream>())
94
41
    }
95
44
}
96

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

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

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

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

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