]> git.lizzy.rs Git - rust.git/blob - src/libserialize/base64.rs
auto merge of #13255 : alexcrichton/rust/issue-5605, r=huonw
[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] = bytes!("ABCDEFGHIJKLMNOPQRSTUVWXYZ",
46                                              "abcdefghijklmnopqrstuvwxyz",
47                                              "0123456789+/");
48
49 static URLSAFE_CHARS: &'static[u8] = bytes!("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) -> ~str;
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) -> ~str {
77         let bytes = match config.char_set {
78             Standard => STANDARD_CHARS,
79             UrlSafe => URLSAFE_CHARS
80         };
81
82         let mut v: ~[u8] = ~[];
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_owned(v)
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<~[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.buf, "Invalid character '{}' at position {}", ch, idx),
174             InvalidBase64Length => write!(f.buf, "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 `from_utf8_owned` function in `std::str`
185      * to turn a `[u8]` into a string with characters corresponding to those
186      * values.
187      *
188      * # Example
189      *
190      * This converts a string literal to base64 and back.
191      *
192      * ```rust
193      * extern crate serialize;
194      * use serialize::base64::{ToBase64, FromBase64, STANDARD};
195      * use std::str;
196      *
197      * fn main () {
198      *     let hello_str = bytes!("Hello, World").to_base64(STANDARD);
199      *     println!("base64 output: {}", hello_str);
200      *     let res = hello_str.from_base64();
201      *     if res.is_ok() {
202      *       let opt_bytes = str::from_utf8_owned(res.unwrap());
203      *       if opt_bytes.is_some() {
204      *         println!("decoded from base64: {}", opt_bytes.unwrap());
205      *       }
206      *     }
207      * }
208      * ```
209      */
210     fn from_base64(&self) -> Result<~[u8], FromBase64Error> {
211         let mut r = ~[];
212         let mut buf: u32 = 0;
213         let mut modulus = 0;
214
215         let mut it = self.bytes().enumerate();
216         for (idx, byte) in it {
217             let val = byte as u32;
218
219             match byte as char {
220                 'A'..'Z' => buf |= val - 0x41,
221                 'a'..'z' => buf |= val - 0x47,
222                 '0'..'9' => buf |= val + 0x04,
223                 '+'|'-' => buf |= 0x3E,
224                 '/'|'_' => buf |= 0x3F,
225                 '\r'|'\n' => continue,
226                 '=' => break,
227                 _ => return Err(InvalidBase64Character(self.char_at(idx), idx)),
228             }
229
230             buf <<= 6;
231             modulus += 1;
232             if modulus == 4 {
233                 modulus = 0;
234                 r.push((buf >> 22) as u8);
235                 r.push((buf >> 14) as u8);
236                 r.push((buf >> 6 ) as u8);
237             }
238         }
239
240         for (idx, byte) in it {
241             match byte as char {
242                 '='|'\r'|'\n' => continue,
243                 _ => return Err(InvalidBase64Character(self.char_at(idx), idx)),
244             }
245         }
246
247         match modulus {
248             2 => {
249                 r.push((buf >> 10) as u8);
250             }
251             3 => {
252                 r.push((buf >> 16) as u8);
253                 r.push((buf >> 8 ) as u8);
254             }
255             0 => (),
256             _ => return Err(InvalidBase64Length),
257         }
258
259         Ok(r)
260     }
261 }
262
263 #[cfg(test)]
264 mod tests {
265     extern crate test;
266     extern crate rand;
267     use self::test::BenchHarness;
268     use base64::{Config, FromBase64, ToBase64, STANDARD, URL_SAFE};
269
270     #[test]
271     fn test_to_base64_basic() {
272         assert_eq!("".as_bytes().to_base64(STANDARD), ~"");
273         assert_eq!("f".as_bytes().to_base64(STANDARD), ~"Zg==");
274         assert_eq!("fo".as_bytes().to_base64(STANDARD), ~"Zm8=");
275         assert_eq!("foo".as_bytes().to_base64(STANDARD), ~"Zm9v");
276         assert_eq!("foob".as_bytes().to_base64(STANDARD), ~"Zm9vYg==");
277         assert_eq!("fooba".as_bytes().to_base64(STANDARD), ~"Zm9vYmE=");
278         assert_eq!("foobar".as_bytes().to_base64(STANDARD), ~"Zm9vYmFy");
279     }
280
281     #[test]
282     fn test_to_base64_line_break() {
283         assert!(![0u8, ..1000].to_base64(Config {line_length: None, ..STANDARD})
284                 .contains("\r\n"));
285         assert_eq!("foobar".as_bytes().to_base64(Config {line_length: Some(4),
286                                                          ..STANDARD}),
287                    ~"Zm9v\r\nYmFy");
288     }
289
290     #[test]
291     fn test_to_base64_padding() {
292         assert_eq!("f".as_bytes().to_base64(Config {pad: false, ..STANDARD}), ~"Zg");
293         assert_eq!("fo".as_bytes().to_base64(Config {pad: false, ..STANDARD}), ~"Zm8");
294     }
295
296     #[test]
297     fn test_to_base64_url_safe() {
298         assert_eq!([251, 255].to_base64(URL_SAFE), ~"-_8");
299         assert_eq!([251, 255].to_base64(STANDARD), ~"+/8=");
300     }
301
302     #[test]
303     fn test_from_base64_basic() {
304         assert_eq!("".from_base64().unwrap(), "".as_bytes().to_owned());
305         assert_eq!("Zg==".from_base64().unwrap(), "f".as_bytes().to_owned());
306         assert_eq!("Zm8=".from_base64().unwrap(), "fo".as_bytes().to_owned());
307         assert_eq!("Zm9v".from_base64().unwrap(), "foo".as_bytes().to_owned());
308         assert_eq!("Zm9vYg==".from_base64().unwrap(), "foob".as_bytes().to_owned());
309         assert_eq!("Zm9vYmE=".from_base64().unwrap(), "fooba".as_bytes().to_owned());
310         assert_eq!("Zm9vYmFy".from_base64().unwrap(), "foobar".as_bytes().to_owned());
311     }
312
313     #[test]
314     fn test_from_base64_newlines() {
315         assert_eq!("Zm9v\r\nYmFy".from_base64().unwrap(),
316                    "foobar".as_bytes().to_owned());
317         assert_eq!("Zm9vYg==\r\n".from_base64().unwrap(),
318                    "foob".as_bytes().to_owned());
319     }
320
321     #[test]
322     fn test_from_base64_urlsafe() {
323         assert_eq!("-_8".from_base64().unwrap(), "+/8=".from_base64().unwrap());
324     }
325
326     #[test]
327     fn test_from_base64_invalid_char() {
328         assert!("Zm$=".from_base64().is_err())
329         assert!("Zg==$".from_base64().is_err());
330     }
331
332     #[test]
333     fn test_from_base64_invalid_padding() {
334         assert!("Z===".from_base64().is_err());
335     }
336
337     #[test]
338     fn test_base64_random() {
339         use self::rand::{task_rng, random, Rng};
340         use std::slice;
341
342         for _ in range(0, 1000) {
343             let times = task_rng().gen_range(1u, 100);
344             let v = slice::from_fn(times, |_| random::<u8>());
345             assert_eq!(v.to_base64(STANDARD).from_base64().unwrap(), v);
346         }
347     }
348
349     #[bench]
350     pub fn bench_to_base64(bh: & mut BenchHarness) {
351         let s = "イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム \
352                  ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン";
353         bh.iter(|| {
354             s.as_bytes().to_base64(STANDARD);
355         });
356         bh.bytes = s.len() as u64;
357     }
358
359     #[bench]
360     pub fn bench_from_base64(bh: & mut BenchHarness) {
361         let s = "イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム \
362                  ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン";
363         let b = s.as_bytes().to_base64(STANDARD);
364         bh.iter(|| {
365             b.from_base64().unwrap();
366         });
367         bh.bytes = b.len() as u64;
368     }
369
370 }