]> git.lizzy.rs Git - rust.git/blob - src/libserialize/base64.rs
auto merge of #12705 : alexcrichton/rust/issue-12692, r=brson
[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     char_set: CharacterSet,
27     /// True to pad output with `=` characters
28     pad: bool,
29     /// `Some(len)` to wrap lines at `len`, `None` to disable line wrapping
30     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]);
103             v.push(bytes[(n >> 12) & 63]);
104             v.push(bytes[(n >> 6 ) & 63]);
105             v.push(bytes[n & 63]);
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]);
129                 v.push(bytes[(n >> 12) & 63]);
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]);
139                 v.push(bytes[(n >> 12) & 63]);
140                 v.push(bytes[(n >> 6 ) & 63]);
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     use self::test::BenchHarness;
267     use base64::{Config, FromBase64, ToBase64, STANDARD, URL_SAFE};
268
269     #[test]
270     fn test_to_base64_basic() {
271         assert_eq!("".as_bytes().to_base64(STANDARD), ~"");
272         assert_eq!("f".as_bytes().to_base64(STANDARD), ~"Zg==");
273         assert_eq!("fo".as_bytes().to_base64(STANDARD), ~"Zm8=");
274         assert_eq!("foo".as_bytes().to_base64(STANDARD), ~"Zm9v");
275         assert_eq!("foob".as_bytes().to_base64(STANDARD), ~"Zm9vYg==");
276         assert_eq!("fooba".as_bytes().to_base64(STANDARD), ~"Zm9vYmE=");
277         assert_eq!("foobar".as_bytes().to_base64(STANDARD), ~"Zm9vYmFy");
278     }
279
280     #[test]
281     fn test_to_base64_line_break() {
282         assert!(![0u8, ..1000].to_base64(Config {line_length: None, ..STANDARD})
283                 .contains("\r\n"));
284         assert_eq!("foobar".as_bytes().to_base64(Config {line_length: Some(4),
285                                                          ..STANDARD}),
286                    ~"Zm9v\r\nYmFy");
287     }
288
289     #[test]
290     fn test_to_base64_padding() {
291         assert_eq!("f".as_bytes().to_base64(Config {pad: false, ..STANDARD}), ~"Zg");
292         assert_eq!("fo".as_bytes().to_base64(Config {pad: false, ..STANDARD}), ~"Zm8");
293     }
294
295     #[test]
296     fn test_to_base64_url_safe() {
297         assert_eq!([251, 255].to_base64(URL_SAFE), ~"-_8");
298         assert_eq!([251, 255].to_base64(STANDARD), ~"+/8=");
299     }
300
301     #[test]
302     fn test_from_base64_basic() {
303         assert_eq!("".from_base64().unwrap(), "".as_bytes().to_owned());
304         assert_eq!("Zg==".from_base64().unwrap(), "f".as_bytes().to_owned());
305         assert_eq!("Zm8=".from_base64().unwrap(), "fo".as_bytes().to_owned());
306         assert_eq!("Zm9v".from_base64().unwrap(), "foo".as_bytes().to_owned());
307         assert_eq!("Zm9vYg==".from_base64().unwrap(), "foob".as_bytes().to_owned());
308         assert_eq!("Zm9vYmE=".from_base64().unwrap(), "fooba".as_bytes().to_owned());
309         assert_eq!("Zm9vYmFy".from_base64().unwrap(), "foobar".as_bytes().to_owned());
310     }
311
312     #[test]
313     fn test_from_base64_newlines() {
314         assert_eq!("Zm9v\r\nYmFy".from_base64().unwrap(),
315                    "foobar".as_bytes().to_owned());
316         assert_eq!("Zm9vYg==\r\n".from_base64().unwrap(),
317                    "foob".as_bytes().to_owned());
318     }
319
320     #[test]
321     fn test_from_base64_urlsafe() {
322         assert_eq!("-_8".from_base64().unwrap(), "+/8=".from_base64().unwrap());
323     }
324
325     #[test]
326     fn test_from_base64_invalid_char() {
327         assert!("Zm$=".from_base64().is_err())
328         assert!("Zg==$".from_base64().is_err());
329     }
330
331     #[test]
332     fn test_from_base64_invalid_padding() {
333         assert!("Z===".from_base64().is_err());
334     }
335
336     #[test]
337     fn test_base64_random() {
338         use std::rand::{task_rng, random, Rng};
339         use std::vec;
340
341         for _ in range(0, 1000) {
342             let times = task_rng().gen_range(1u, 100);
343             let v = vec::from_fn(times, |_| random::<u8>());
344             assert_eq!(v.to_base64(STANDARD).from_base64().unwrap(), v);
345         }
346     }
347
348     #[bench]
349     pub fn bench_to_base64(bh: & mut BenchHarness) {
350         let s = "イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム \
351                  ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン";
352         bh.iter(|| {
353             s.as_bytes().to_base64(STANDARD);
354         });
355         bh.bytes = s.len() as u64;
356     }
357
358     #[bench]
359     pub fn bench_from_base64(bh: & mut BenchHarness) {
360         let s = "イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム \
361                  ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン";
362         let b = s.as_bytes().to_base64(STANDARD);
363         bh.iter(|| {
364             b.from_base64().unwrap();
365         });
366         bh.bytes = b.len() as u64;
367     }
368
369 }