Explorar el Código

Use a closure and a generic error type rather than io::Write

Simon Sapin hace 8 años
padre
commit
1cff0dd6cc
Se han modificado 1 ficheros con 82 adiciones y 97 borrados
  1. 82 97
      src/lib.rs

+ 82 - 97
src/lib.rs

@@ -1,8 +1,6 @@
 #[macro_use] extern crate matches;
 pub extern crate mime;
 
-use std::io;
-
 pub enum DataUrlError {
     NotADataUrl,
     NoComma,
@@ -14,7 +12,16 @@ pub struct DataUrl<'a> {
     encoded_body_plus_fragment: &'a str,
 }
 
-pub struct Base64Error(());
+pub enum DecodeError<E> {
+    InvalidBase64(InvalidBase64),
+    WriteError(E),
+}
+
+pub struct InvalidBase64(());
+
+impl<E> From<InvalidBase64> for DecodeError<E> {
+    fn from(e: InvalidBase64) -> Self { DecodeError::InvalidBase64(e) }
+}
 
 impl<'a> DataUrl<'a> {
     /// <https://fetch.spec.whatwg.org/#data-url-processor>
@@ -36,30 +43,32 @@ impl<'a> DataUrl<'a> {
         &self.mime_type
     }
 
-    /// Streaming-decode the data URL’s body to `sink`.
-    ///
-    /// Errors while writing to the sink are propagated.
-    /// Invalid base64 causes an error with `e.kind() == ErrorKind::InvalidData`.
-    /// When decoding without error, the URL’s fragment identifier is returned if it has one.
+    /// Streaming-decode the data URL’s body to `write_body_bytes`,
+    /// and return the URL’s fragment identifier is returned if it has one.
     ///
-    /// The fragment identifier is represented as in the origin input.
+    /// The fragment is represented as in the origin input.
     /// It needs to be either percent-encoded to obtain the same string as in a parsed URL,
     /// or percent-decoded to interpret it as text.
-    pub fn decode_body<W>(&self, sink: W) -> io::Result<Result<Option<&'a str>, Base64Error>>
-        where W: io::Write
+    pub fn decode<F, E>(&self, write_body_bytes: F) -> Result<Option<&'a str>, DecodeError<E>>
+        where F: FnMut(&[u8]) -> Result<(), E>
     {
         if self.base64 {
-            decode_with_base64(self.encoded_body_plus_fragment, sink)
+            decode_with_base64(self.encoded_body_plus_fragment, write_body_bytes)
         } else {
-            decode_without_base64(self.encoded_body_plus_fragment, sink).map(Ok)
+            decode_without_base64(self.encoded_body_plus_fragment, write_body_bytes)
+                .map_err(DecodeError::WriteError)
         }
     }
 
-    pub fn decode_body_to_vec(&self) -> Result<(Vec<u8>, Option<&str>), Base64Error> {
-        let mut sink = Vec::new();
-        let base64_result = self.decode_body(&mut sink).unwrap();
-        let url_fragment = base64_result?;
-        Ok((sink, url_fragment))
+    pub fn decode_to_vec(&self) -> Result<(Vec<u8>, Option<&str>), InvalidBase64> {
+        enum Impossible {}
+        let mut body = Vec::new();
+        let result = self.decode::<_, Impossible>(|bytes| Ok(body.extend_from_slice(bytes)));
+        match result {
+            Ok(url_fragment) => Ok((body, url_fragment)),
+            Err(DecodeError::InvalidBase64(e)) => Err(e),
+            Err(DecodeError::WriteError(e)) => match e {}
+        }
     }
 }
 
@@ -173,9 +182,9 @@ fn remove_suffix<'a, Eq>(haystack: &'a str, needle: &str, eq: Eq) -> Option<&'a
 /// Anything that would have been UTF-8 percent-encoded by the URL parser
 /// would be percent-decoded here.
 /// We skip that round-trip and pass it through unchanged.
-fn decode_without_base64<W>(encoded_body_plus_fragment: &str, mut sink: W)
-                            -> io::Result<Option<&str>>
-    where W: io::Write
+fn decode_without_base64<F, E>(encoded_body_plus_fragment: &str, mut write_bytes: F)
+                               -> Result<Option<&str>, E>
+    where F: FnMut(&[u8]) -> Result<(), E>
 {
     let bytes = encoded_body_plus_fragment.as_bytes();
     let mut slice_start = 0;
@@ -187,7 +196,7 @@ fn decode_without_base64<W>(encoded_body_plus_fragment: &str, mut sink: W)
             // Write everything (if anything) "non-special" we’ve accumulated
             // before this special byte
             if i > slice_start {
-                sink.write_all(&bytes[slice_start..i])?;
+                write_bytes(&bytes[slice_start..i])?;
             }
             // Then deal with the special byte.
             match byte {
@@ -197,7 +206,7 @@ fn decode_without_base64<W>(encoded_body_plus_fragment: &str, mut sink: W)
                     if let (Some(h), Some(l)) = (h, l) {
                         // '%' followed by two ASCII hex digits
                         let one_byte = h as u8 * 0x10 + l as u8;
-                        sink.write_all(&[one_byte])?;
+                        write_bytes(&[one_byte])?;
                         slice_start = i + 3;
                     } else {
                         // Do nothing. Leave slice_start unchanged.
@@ -215,72 +224,23 @@ fn decode_without_base64<W>(encoded_body_plus_fragment: &str, mut sink: W)
             }
         }
     }
-    sink.write_all(&bytes[slice_start..])?;
+    write_bytes(&bytes[slice_start..])?;
     Ok(None)
 }
 
 /// `decode_without_base64()` composed with
 /// <https://infra.spec.whatwg.org/#isomorphic-decode> composed with
 /// <https://infra.spec.whatwg.org/#forgiving-base64-decode>.
-fn decode_with_base64<W>(encoded_body_plus_fragment: &str, sink: W)
-                          -> io::Result<Result<Option<&str>, Base64Error>>
-    where W: io::Write
+fn decode_with_base64<F, E>(encoded_body_plus_fragment: &str, mut write_bytes: F)
+                            -> Result<Option<&str>, DecodeError<E>>
+    where F: FnMut(&[u8]) -> Result<(), E>
 {
-    let mut decoder = Base64Decoder {
-        sink,
-        bit_buffer: 0,
-        buffer_bit_length: 0,
-        padding_symbols: 0,
-        base64_error: false,
-    };
-    let result = decode_without_base64(encoded_body_plus_fragment, &mut decoder);
-    if decoder.base64_error {
-        return Ok(Err(Base64Error(())))
-    }
-    let fragment = result?;
-    match (decoder.buffer_bit_length, decoder.padding_symbols) {
-        (0, 0) => {
-            // A multiple of four of alphabet symbols, and nothing else.
-        }
-        (12, 2) | (12, 0) => {
-            // A multiple of four of alphabet symbols, followed by two more symbols,
-            // optionally followed by two padding characters (which make a total multiple of four).
-            let byte_buffer = [
-                (decoder.bit_buffer >> 4) as u8,
-            ];
-            decoder.sink.write_all(&byte_buffer)?;
-        }
-        (18, 1) | (18, 0) => {
-            // A multiple of four of alphabet symbols, followed by three more symbols,
-            // optionally followed by one padding character (which make a total multiple of four).
-            let byte_buffer = [
-                (decoder.bit_buffer >> 10) as u8,
-                (decoder.bit_buffer >> 2) as u8,
-            ];
-            decoder.sink.write_all(&byte_buffer)?;
-        }
-        _ => {
-            // No other combination is acceptable
-            return Ok(Err(Base64Error(())))
-        }
-    }
-    Ok(Ok(fragment))
-}
-
-struct Base64Decoder<W> {
-    sink: W,
-    bit_buffer: u32,
-    buffer_bit_length: u8,
-    padding_symbols: u8,
-    base64_error: bool,
-}
-
-impl<W> io::Write for Base64Decoder<W> where W: io::Write {
-    fn write(&mut self, _buf: &[u8]) -> io::Result<usize> { unimplemented!() }
-    fn flush(&mut self) -> io::Result<()> { unimplemented!() }
+    let mut bit_buffer: u32 = 0;
+    let mut buffer_bit_length: u8 = 0;
+    let mut padding_symbols: u8 = 0;
 
-    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
-        for &byte in buf.iter() {
+    let fragment = decode_without_base64::<_, DecodeError<E>>(encoded_body_plus_fragment, |bytes| {
+        for &byte in bytes.iter() {
             let value = BASE64_DECODE_TABLE[byte as usize];
             if value < 0 {
                 // A character that’s not part of the alphabet
@@ -292,37 +252,62 @@ impl<W> io::Write for Base64Decoder<W> where W: io::Write {
                 }
 
                 if byte == b'=' {
-                    self.padding_symbols = self.padding_symbols.saturating_add(8);
+                    padding_symbols = padding_symbols.saturating_add(8);
                     continue
                 }
 
-                self.base64_error = true;
-                Err(io::ErrorKind::InvalidData)?
+                Err(InvalidBase64(()))?
             }
-            if self.padding_symbols > 0 {
+            if padding_symbols > 0 {
                 // Alphabet symbols after padding
-                self.base64_error = true;
-                Err(io::ErrorKind::InvalidData)?
+                Err(InvalidBase64(()))?
             }
-            self.bit_buffer <<= 6;
-            self.bit_buffer |= value as u32;
-            if self.buffer_bit_length < 24 {
-                self.buffer_bit_length += 6;
+            bit_buffer <<= 6;
+            bit_buffer |= value as u32;
+            if buffer_bit_length < 24 {
+                buffer_bit_length += 6;
             } else {
                 // We’ve accumulated four times 6 bits, which equals three times 8 bits.
                 let byte_buffer = [
-                    (self.bit_buffer >> 16) as u8,
-                    (self.bit_buffer >> 8) as u8,
-                    self.bit_buffer as u8,
+                    (bit_buffer >> 16) as u8,
+                    (bit_buffer >> 8) as u8,
+                    bit_buffer as u8,
                 ];
-                self.sink.write_all(&byte_buffer)?;
-                self.buffer_bit_length = 0;
-                // No need to reset self.bit_buffer,
+                write_bytes(&byte_buffer).map_err(DecodeError::WriteError)?;
+                buffer_bit_length = 0;
+                // No need to reset bit_buffer,
                 // since next time we’re only gonna read relevant bits.
             }
         }
         Ok(())
+    })?;
+    match (buffer_bit_length, padding_symbols) {
+        (0, 0) => {
+            // A multiple of four of alphabet symbols, and nothing else.
+        }
+        (12, 2) | (12, 0) => {
+            // A multiple of four of alphabet symbols, followed by two more symbols,
+            // optionally followed by two padding characters (which make a total multiple of four).
+            let byte_buffer = [
+                (bit_buffer >> 4) as u8,
+            ];
+            write_bytes(&byte_buffer).map_err(DecodeError::WriteError)?;
+        }
+        (18, 1) | (18, 0) => {
+            // A multiple of four of alphabet symbols, followed by three more symbols,
+            // optionally followed by one padding character (which make a total multiple of four).
+            let byte_buffer = [
+                (bit_buffer >> 10) as u8,
+                (bit_buffer >> 2) as u8,
+            ];
+            write_bytes(&byte_buffer).map_err(DecodeError::WriteError)?;
+        }
+        _ => {
+            // No other combination is acceptable
+            Err(InvalidBase64(()))?
+        }
     }
+    Ok(fragment)
 }
 
 /// Generated by `make_base64_decode_table.py` based on "Table 1: The Base 64 Alphabet"