1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//! Utilities to acquire streams and data from from URLs.

use data_url::{mime::Mime, DataUrl};
use gio::{
    prelude::{FileExt, FileExtManual},
    Cancellable, File as GFile, InputStream, MemoryInputStream,
};
use glib::{self, object::Cast, Bytes as GBytes};
use std::fmt;
use std::str::FromStr;

use crate::url_resolver::AllowedUrl;

pub enum IoError {
    BadDataUrl,
    Glib(glib::Error),
}

impl From<glib::Error> for IoError {
    fn from(e: glib::Error) -> IoError {
        IoError::Glib(e)
    }
}

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

pub struct BinaryData {
    pub data: Vec<u8>,
    pub mime_type: Mime,
}

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

    let mime = data_url.mime_type();

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

    let mime_type = Mime {
        type_: mime.type_.clone(),
        subtype: mime.subtype.clone(),
        parameters: mime.parameters.clone(),
    };

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

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

    Ok(BinaryData {
        data: bytes,
        mime_type,
    })
}

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

    if uri.starts_with("data:") {
        let BinaryData { data, .. } = decode_data_uri(uri)?;

        //        {
        //            use std::fs::File;
        //            use std::io::prelude::*;
        //
        //            let mut file = File::create("data.bin").unwrap();
        //            file.write_all(&data).unwrap();
        //        }

        let stream = MemoryInputStream::from_bytes(&GBytes::from_owned(data));
        Ok(stream.upcast::<InputStream>())
    } else {
        let file = GFile::for_uri(uri);
        let stream = file.read(cancellable)?;

        Ok(stream.upcast::<InputStream>())
    }
}

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

    if uri.starts_with("data:") {
        Ok(decode_data_uri(uri)?)
    } else {
        let file = GFile::for_uri(uri);
        let (contents, _etag) = file.load_contents(cancellable)?;

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

        let mime_type = if let Some(mime_type_str) = gio::content_type_get_mime_type(&content_type)
        {
            Mime::from_str(&mime_type_str)
                .expect("gio::content_type_get_mime_type returned an invalid MIME-type!?")
        } else {
            Mime::from_str("application/octet-stream").unwrap()
        };

        Ok(BinaryData {
            data: contents.to_vec(),
            mime_type,
        })
    }
}