|
| 1 | +pub mod data; |
| 2 | +pub mod error; |
| 3 | +pub mod mime; |
| 4 | +pub mod status; |
| 5 | + |
| 6 | +pub use data::Data; |
| 7 | +pub use error::Error; |
| 8 | +pub use mime::Mime; |
| 9 | +pub use status::Status; |
| 10 | + |
| 11 | +use gio::{ |
| 12 | + prelude::{IOStreamExt, InputStreamExt}, |
| 13 | + Cancellable, SocketConnection, |
| 14 | +}; |
| 15 | +use glib::{Bytes, Priority}; |
| 16 | + |
| 17 | +pub const MAX_LEN: usize = 0x400; // 1024 |
| 18 | + |
| 19 | +pub struct Meta { |
| 20 | + data: Data, |
| 21 | + mime: Mime, |
| 22 | + status: Status, |
| 23 | + // @TODO |
| 24 | + // charset: Charset, |
| 25 | + // language: Language, |
| 26 | +} |
| 27 | + |
| 28 | +impl Meta { |
| 29 | + // Constructors |
| 30 | + |
| 31 | + /// Create new `Self` from UTF-8 buffer |
| 32 | + pub fn from_utf8(buffer: &[u8]) -> Result<Self, (Error, Option<&str>)> { |
| 33 | + let len = buffer.len(); |
| 34 | + |
| 35 | + match buffer.get(..if len > MAX_LEN { MAX_LEN } else { len }) { |
| 36 | + Some(slice) => { |
| 37 | + // Parse data |
| 38 | + let data = Data::from_utf8(&slice); |
| 39 | + |
| 40 | + if let Err(reason) = data { |
| 41 | + return Err(( |
| 42 | + match reason { |
| 43 | + data::Error::Decode => Error::DataDecode, |
| 44 | + data::Error::Protocol => Error::DataProtocol, |
| 45 | + }, |
| 46 | + None, |
| 47 | + )); |
| 48 | + } |
| 49 | + |
| 50 | + // MIME |
| 51 | + |
| 52 | + let mime = Mime::from_utf8(&slice); |
| 53 | + |
| 54 | + if let Err(reason) = mime { |
| 55 | + return Err(( |
| 56 | + match reason { |
| 57 | + mime::Error::Decode => Error::MimeDecode, |
| 58 | + mime::Error::Protocol => Error::MimeProtocol, |
| 59 | + mime::Error::Undefined => Error::MimeUndefined, |
| 60 | + }, |
| 61 | + None, |
| 62 | + )); |
| 63 | + } |
| 64 | + |
| 65 | + // Status |
| 66 | + |
| 67 | + let status = Status::from_utf8(&slice); |
| 68 | + |
| 69 | + if let Err(reason) = status { |
| 70 | + return Err(( |
| 71 | + match reason { |
| 72 | + status::Error::Decode => Error::StatusDecode, |
| 73 | + status::Error::Protocol => Error::StatusProtocol, |
| 74 | + status::Error::Undefined => Error::StatusUndefined, |
| 75 | + }, |
| 76 | + None, |
| 77 | + )); |
| 78 | + } |
| 79 | + |
| 80 | + Ok(Self { |
| 81 | + data: data.unwrap(), |
| 82 | + mime: mime.unwrap(), |
| 83 | + status: status.unwrap(), |
| 84 | + }) |
| 85 | + } |
| 86 | + None => Err((Error::Protocol, None)), |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + /// Asynchronously create new `Self` from [InputStream](https://docs.gtk.org/gio/class.InputStream.html) |
| 91 | + /// for given [SocketConnection](https://docs.gtk.org/gio/class.SocketConnection.html) |
| 92 | + pub fn from_socket_connection_async( |
| 93 | + socket_connection: SocketConnection, |
| 94 | + priority: Option<Priority>, |
| 95 | + cancellable: Option<Cancellable>, |
| 96 | + on_complete: impl FnOnce(Result<Self, (Error, Option<&str>)>) + 'static, |
| 97 | + ) { |
| 98 | + read_from_socket_connection_async( |
| 99 | + Vec::with_capacity(MAX_LEN), |
| 100 | + socket_connection, |
| 101 | + match cancellable { |
| 102 | + Some(value) => Some(value), |
| 103 | + None => None::<Cancellable>, |
| 104 | + }, |
| 105 | + match priority { |
| 106 | + Some(value) => value, |
| 107 | + None => Priority::DEFAULT, |
| 108 | + }, |
| 109 | + |result| match result { |
| 110 | + Ok(buffer) => on_complete(Self::from_utf8(&buffer)), |
| 111 | + Err(reason) => on_complete(Err(reason)), |
| 112 | + }, |
| 113 | + ); |
| 114 | + } |
| 115 | + |
| 116 | + // Getters |
| 117 | + |
| 118 | + pub fn status(&self) -> &Status { |
| 119 | + &self.status |
| 120 | + } |
| 121 | + |
| 122 | + pub fn data(&self) -> &Data { |
| 123 | + &self.data |
| 124 | + } |
| 125 | + |
| 126 | + pub fn mime(&self) -> &Mime { |
| 127 | + &self.mime |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | +// Tools |
| 132 | + |
| 133 | +/// Asynchronously take meta bytes from [InputStream](https://docs.gtk.org/gio/class.InputStream.html) |
| 134 | +/// for given [SocketConnection](https://docs.gtk.org/gio/class.SocketConnection.html) |
| 135 | +/// |
| 136 | +/// * this function implements low-level helper for `Meta::from_socket_connection_async`, also provides public API for external integrations |
| 137 | +/// * requires entire `SocketConnection` instead of `InputStream` to keep connection alive in async context |
| 138 | +pub fn read_from_socket_connection_async( |
| 139 | + mut buffer: Vec<Bytes>, |
| 140 | + connection: SocketConnection, |
| 141 | + cancellable: Option<Cancellable>, |
| 142 | + priority: Priority, |
| 143 | + on_complete: impl FnOnce(Result<Vec<u8>, (Error, Option<&str>)>) + 'static, |
| 144 | +) { |
| 145 | + connection.input_stream().read_bytes_async( |
| 146 | + 1, // do not change! |
| 147 | + priority, |
| 148 | + cancellable.clone().as_ref(), |
| 149 | + move |result| match result { |
| 150 | + Ok(bytes) => { |
| 151 | + // Expect valid header length |
| 152 | + if bytes.len() == 0 || buffer.len() >= MAX_LEN { |
| 153 | + return on_complete(Err((Error::Protocol, None))); |
| 154 | + } |
| 155 | + |
| 156 | + // Read next byte without buffer record |
| 157 | + if bytes.contains(&b'\r') { |
| 158 | + return read_from_socket_connection_async( |
| 159 | + buffer, |
| 160 | + connection, |
| 161 | + cancellable, |
| 162 | + priority, |
| 163 | + on_complete, |
| 164 | + ); |
| 165 | + } |
| 166 | + |
| 167 | + // Complete without buffer record |
| 168 | + if bytes.contains(&b'\n') { |
| 169 | + return on_complete(Ok(buffer |
| 170 | + .iter() |
| 171 | + .flat_map(|byte| byte.iter()) |
| 172 | + .cloned() |
| 173 | + .collect())); // convert to UTF-8 |
| 174 | + } |
| 175 | + |
| 176 | + // Record |
| 177 | + buffer.push(bytes); |
| 178 | + |
| 179 | + // Continue |
| 180 | + read_from_socket_connection_async( |
| 181 | + buffer, |
| 182 | + connection, |
| 183 | + cancellable, |
| 184 | + priority, |
| 185 | + on_complete, |
| 186 | + ); |
| 187 | + } |
| 188 | + Err(reason) => on_complete(Err((Error::InputStream, Some(reason.message())))), |
| 189 | + }, |
| 190 | + ); |
| 191 | +} |
0 commit comments