]> git.lizzy.rs Git - rust.git/blob - src/libserialize/base64.rs
librustc: Remove the fallback to `int` from typechecking.
[rust.git] / src / libserialize / base64.rs
1 // Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 //! Base64 binary-to-text encoding
12 use std::str;
13 use std::fmt;
14
15 /// Available encoding character sets
16 pub enum CharacterSet {
17     /// The standard character set (uses `+` and `/`)
18     Standard,
19     /// The URL safe character set (uses `-` and `_`)
20     UrlSafe
21 }
22
23 /// Contains configuration parameters for `to_base64`.
24 pub struct Config {
25     /// Character set to use
26     pub char_set: CharacterSet,
27     /// True to pad output with `=` characters
28     pub pad: bool,
29     /// `Some(len)` to wrap lines at `len`, `None` to disable line wrapping
30     pub line_length: Option<uint>
31 }
32
33 /// Configuration for RFC 4648 standard base64 encoding
34 pub static STANDARD: Config =
35     Config {char_set: Standard, pad: true, line_length: None};
36
37 /// Configuration for RFC 4648 base64url encoding
38 pub static URL_SAFE: Config =
39     Config {char_set: UrlSafe, pad: false, line_length: None};
40
41 /// Configuration for RFC 2045 MIME base64 encoding
42 pub static MIME: Config =
43     Config {char_set: Standard, pad: true, line_length: Some(76)};
44
45 static STANDARD_CHARS: &'static[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
46                                         abcdefghijklmnopqrstuvwxyz\
47                                         0123456789+/";
48
49 static URLSAFE_CHARS: &'static[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
50                                        abcdefghijklmnopqrstuvwxyz\
51                                        0123456789-_";
52
53 /// A trait for converting a value to base64 encoding.
54 pub trait ToBase64 {
55     /// Converts the value of `self` to a base64 value following the specified
56     /// format configuration, returning the owned string.
57     fn to_base64(&self, config: Config) -> String;
58 }
59
60 impl<'a> ToBase64 for &'a [u8] {
61     /**
62      * Turn a vector of `u8` bytes into a base64 string.
63      *
64      * # Example
65      *
66      * ```rust
67      * extern crate serialize;
68      * use serialize::base64::{ToBase64, STANDARD};
69      *
70      * fn main () {
71      *     let str = [52,32].to_base64(STANDARD);
72      *     println!("base 64 output: {}", str);
73      * }
74      * ```
75      */
76     fn to_base64(&self, config: Config) -> String {
77         let bytes = match config.char_set {
78             Standard => STANDARD_CHARS,
79             UrlSafe => URLSAFE_CHARS
80         };
81
82         let mut v = Vec::new();
83         let mut i = 0;
84         let mut cur_length = 0;
85         let len = self.len();
86         while i < len - (len % 3) {
87             match config.line_length {
88                 Some(line_length) =>
89                     if cur_length >= line_length {
90                         v.push('\r' as u8);
91                         v.push('\n' as u8);
92                         cur_length = 0;
93                     },
94                 None => ()
95             }
96
97             let n = (self[i] as u32) << 16 |
98                     (self[i + 1] as u32) << 8 |
99                     (self[i + 2] as u32);
100
101             // This 24-bit number gets separated into four 6-bit numbers.
102             v.push(bytes[((n >> 18) & 63) as uint]);
103             v.push(bytes[((n >> 12) & 63) as uint]);
104             v.push(bytes[((n >> 6 ) & 63) as uint]);
105             v.push(bytes[(n & 63) as uint]);
106
107             cur_length += 4;
108             i += 3;
109         }
110
111         if len % 3 != 0 {
112             match config.line_length {
113                 Some(line_length) =>
114                     if cur_length >= line_length {
115                         v.push('\r' as u8);
116                         v.push('\n' as u8);
117                     },
118                 None => ()
119             }
120         }
121
122         // Heh, would be cool if we knew this was exhaustive
123         // (the dream of bounded integer types)
124         match len % 3 {
125             0 => (),
126             1 => {
127                 let n = (self[i] as u32) << 16;
128                 v.push(bytes[((n >> 18) & 63) as uint]);
129                 v.push(bytes[((n >> 12) & 63) as uint]);
130                 if config.pad {
131                     v.push('=' as u8);
132                     v.push('=' as u8);
133                 }
134             }
135             2 => {
136                 let n = (self[i] as u32) << 16 |
137                     (self[i + 1u] as u32) << 8;
138                 v.push(bytes[((n >> 18) & 63) as uint]);
139                 v.push(bytes[((n >> 12) & 63) as uint]);
140                 v.push(bytes[((n >> 6 ) & 63) as uint]);
141                 if config.pad {
142                     v.push('=' as u8);
143                 }
144             }
145             _ => fail!("Algebra is broken, please alert the math police")
146         }
147
148         unsafe {
149             str::raw::from_utf8(v.as_slice()).to_string()
150         }
151     }
152 }
153
154 /// A trait for converting from base64 encoded values.
155 pub trait FromBase64 {
156     /// Converts the value of `self`, interpreted as base64 encoded data, into
157     /// an owned vector of bytes, returning the vector.
158     fn from_base64(&self) -> Result<Vec<u8>, FromBase64Error>;
159 }
160
161 /// Errors that can occur when decoding a base64 encoded string
162 pub enum FromBase64Error {
163     /// The input contained a character not part of the base64 format
164     InvalidBase64Character(char, uint),
165     /// The input had an invalid length
166     InvalidBase64Length,
167 }
168
169 impl fmt::Show for FromBase64Error {
170     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
171         match *self {
172             InvalidBase64Character(ch, idx) =>
173                 write!(f, "Invalid character '{}' at position {}", ch, idx),
174             InvalidBase64Length => write!(f, "Invalid length"),
175         }
176     }
177 }
178
179 impl<'a> FromBase64 for &'a str {
180     /**
181      * Convert any base64 encoded string (literal, `@`, `&`, or `~`)
182      * to the byte values it encodes.
183      *
184      * You can use the `String::from_utf8` function in `std::string` to turn a
185      * `Vec<u8>` into a string with characters corresponding to those values.
186      *
187      * # Example
188      *
189      * This converts a string literal to base64 and back.
190      *
191      * ```rust
192      * extern crate serialize;
193      * use serialize::base64::{ToBase64, FromBase64, STANDARD};
194      *
195      * fn main () {
196      *     let hello_str = b"Hello, World".to_base64(STANDARD);
197      *     println!("base64 output: {}", hello_str);
198      *     let res = hello_str.as_slice().from_base64();
199      *     if res.is_ok() {
200      *       let opt_bytes = String::from_utf8(res.unwrap());
201      *       if opt_bytes.is_ok() {
202      *         println!("decoded from base64: {}", opt_bytes.unwrap());
203      *       }
204      *     }
205      * }
206      * ```
207      */
208     fn from_base64(&self) -> Result<Vec<u8>, FromBase64Error> {
209         let mut r = Vec::new();
210         let mut buf: u32 = 0;
211         let mut modulus = 0;
212
213         let mut it = self.bytes().enumerate();
214         for (idx, byte) in it {
215             let val = byte as u32;
216
217             match byte as char {
218                 'A'..'Z' => buf |= val - 0x41,
219                 'a'..'z' => buf |= val - 0x47,
220                 '0'..'9' => buf |= val + 0x04,
221                 '+'|'-' => buf |= 0x3E,
222                 '/'|'_' => buf |= 0x3F,
223                 '\r'|'\n' => continue,
224                 '=' => break,
225                 _ => return Err(InvalidBase64Character(self.char_at(idx), idx)),
226             }
227
228             buf <<= 6;
229             modulus += 1;
230             if modulus == 4 {
231                 modulus = 0;
232                 r.push((buf >> 22) as u8);
233                 r.push((buf >> 14) as u8);
234                 r.push((buf >> 6 ) as u8);
235             }
236         }
237
238         for (idx, byte) in it {
239             match byte as char {
240                 '='|'\r'|'\n' => continue,
241                 _ => return Err(InvalidBase64Character(self.char_at(idx), idx)),
242             }
243         }
244
245         match modulus {
246             2 => {
247                 r.push((buf >> 10) as u8);
248             }
249             3 => {
250                 r.push((buf >> 16) as u8);
251                 r.push((buf >> 8 ) as u8);
252             }
253             0 => (),
254             _ => return Err(InvalidBase64Length),
255         }
256
257         Ok(r)
258     }
259 }
260
261 #[cfg(test)]
262 mod tests {
263     extern crate test;
264     use self::test::Bencher;
265     use base64::{Config, FromBase64, ToBase64, STANDARD, URL_SAFE};
266
267     #[test]
268     fn test_to_base64_basic() {
269         assert_eq!("".as_bytes().to_base64(STANDARD), "".to_string());
270         assert_eq!("f".as_bytes().to_base64(STANDARD), "Zg==".to_string());
271         assert_eq!("fo".as_bytes().to_base64(STANDARD), "Zm8=".to_string());
272         assert_eq!("foo".as_bytes().to_base64(STANDARD), "Zm9v".to_string());
273         assert_eq!("foob".as_bytes().to_base64(STANDARD), "Zm9vYg==".to_string());
274         assert_eq!("fooba".as_bytes().to_base64(STANDARD), "Zm9vYmE=".to_string());
275         assert_eq!("foobar".as_bytes().to_base64(STANDARD), "Zm9vYmFy".to_string());
276     }
277
278     #[test]
279     fn test_to_base64_line_break() {
280         assert!(![0u8, ..1000].to_base64(Config {line_length: None, ..STANDARD})
281                               .as_slice()
282                               .contains("\r\n"));
283         assert_eq!("foobar".as_bytes().to_base64(Config {line_length: Some(4),
284                                                          ..STANDARD}),
285                    "Zm9v\r\nYmFy".to_string());
286     }
287
288     #[test]
289     fn test_to_base64_padding() {
290         assert_eq!("f".as_bytes().to_base64(Config {pad: false, ..STANDARD}), "Zg".to_string());
291         assert_eq!("fo".as_bytes().to_base64(Config {pad: false, ..STANDARD}), "Zm8".to_string());
292     }
293
294     #[test]
295     fn test_to_base64_url_safe() {
296         assert_eq!([251, 255].to_base64(URL_SAFE), "-_8".to_string());
297         assert_eq!([251, 255].to_base64(STANDARD), "+/8=".to_string());
298     }
299
300     #[test]
301     fn test_from_base64_basic() {
302         assert_eq!("".from_base64().unwrap().as_slice(), "".as_bytes());
303         assert_eq!("Zg==".from_base64().unwrap().as_slice(), "f".as_bytes());
304         assert_eq!("Zm8=".from_base64().unwrap().as_slice(), "fo".as_bytes());
305         assert_eq!("Zm9v".from_base64().unwrap().as_slice(), "foo".as_bytes());
306         assert_eq!("Zm9vYg==".from_base64().unwrap().as_slice(), "foob".as_bytes());
307         assert_eq!("Zm9vYmE=".from_base64().unwrap().as_slice(), "fooba".as_bytes());
308         assert_eq!("Zm9vYmFy".from_base64().unwrap().as_slice(), "foobar".as_bytes());
309     }
310
311     #[test]
312     fn test_from_base64_newlines() {
313         assert_eq!("Zm9v\r\nYmFy".from_base64().unwrap().as_slice(),
314                    "foobar".as_bytes());
315         assert_eq!("Zm9vYg==\r\n".from_base64().unwrap().as_slice(),
316                    "foob".as_bytes());
317     }
318
319     #[test]
320     fn test_from_base64_urlsafe() {
321         assert_eq!("-_8".from_base64().unwrap(), "+/8=".from_base64().unwrap());
322     }
323
324     #[test]
325     fn test_from_base64_invalid_char() {
326         assert!("Zm$=".from_base64().is_err())
327         assert!("Zg==$".from_base64().is_err());
328     }
329
330     #[test]
331     fn test_from_base64_invalid_padding() {
332         assert!("Z===".from_base64().is_err());
333     }
334
335     #[test]
336     fn test_base64_random() {
337         use std::rand::{task_rng, random, Rng};
338
339         for _ in range(0u, 1000) {
340             let times = task_rng().gen_range(1u, 100);
341             let v = Vec::from_fn(times, |_| random::<u8>());
342             assert_eq!(v.as_slice()
343                         .to_base64(STANDARD)
344                         .as_slice()
345                         .from_base64()
346                         .unwrap()
347                         .as_slice(),
348                        v.as_slice());
349         }
350     }
351
352     #[bench]
353     pub fn bench_to_base64(b: &mut Bencher) {
354         let s = "イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム \
355                  ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン";
356         b.iter(|| {
357             s.as_bytes().to_base64(STANDARD);
358         });
359         b.bytes = s.len() as u64;
360     }
361
362     #[bench]
363     pub fn bench_from_base64(b: &mut Bencher) {
364         let s = "イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム \
365                  ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン";
366         let sb = s.as_bytes().to_base64(STANDARD);
367         b.iter(|| {
368             sb.as_slice().from_base64().unwrap();
369         });
370         b.bytes = sb.len() as u64;
371     }
372
373 }