1use 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 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 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
68pub 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 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
96pub 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}