]> git.lizzy.rs Git - rust.git/blob - src/libextra/base64.rs
550b891a4db16c22356d20be362f55ca7b06e6cb
[rust.git] / src / libextra / 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
13 /// Available encoding character sets
14 pub enum CharacterSet {
15     /// The standard character set (uses '+' and '/')
16     Standard,
17     /// The URL safe character set (uses '-' and '_')
18     UrlSafe
19 }
20
21 /// Contains configuration parameters for to_base64
22 pub struct Config {
23     /// Character set to use
24     char_set: CharacterSet,
25     /// True to pad output with '=' characters
26     pad: bool,
27     /// Some(len) to wrap lines at len, None to disable line wrapping
28     line_length: Option<uint>
29 }
30
31 /// Configuration for RFC 4648 standard base64 encoding
32 pub static STANDARD: Config =
33     Config {char_set: Standard, pad: true, line_length: None};
34
35 /// Configuration for RFC 4648 base64url encoding
36 pub static URL_SAFE: Config =
37     Config {char_set: UrlSafe, pad: false, line_length: None};
38
39 /// Configuration for RFC 2045 MIME base64 encoding
40 pub static MIME: Config =
41     Config {char_set: Standard, pad: true, line_length: Some(76)};
42
43 static STANDARD_CHARS: [char, ..64] = [
44     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
45     'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
46     'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
47     'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
48     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
49 ];
50
51 static URLSAFE_CHARS: [char, ..64] = [
52     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
53     'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
54     'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
55     'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
56     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
57 ];
58
59 /// A trait for converting a value to base64 encoding.
60 pub trait ToBase64 {
61     /// Converts the value of `self` to a base64 value following the specified
62     /// format configuration, returning the owned string.
63     fn to_base64(&self, config: Config) -> ~str;
64 }
65
66 impl<'self> ToBase64 for &'self [u8] {
67     /**
68      * Turn a vector of `u8` bytes into a base64 string.
69      *
70      * # Example
71      *
72      * ~~~ {.rust}
73      * extern mod extra;
74      * use extra::base64::{ToBase64, standard};
75      *
76      * fn main () {
77      *     let str = [52,32].to_base64(standard);
78      *     printfln!("%s", str);
79      * }
80      * ~~~
81      */
82     fn to_base64(&self, config: Config) -> ~str {
83         let chars = match config.char_set {
84             Standard => STANDARD_CHARS,
85             UrlSafe => URLSAFE_CHARS
86         };
87
88         let mut s = ~"";
89         let mut i = 0;
90         let mut cur_length = 0;
91         let len = self.len();
92         while i < len - (len % 3) {
93             match config.line_length {
94                 Some(line_length) =>
95                     if cur_length >= line_length {
96                         s.push_str("\r\n");
97                         cur_length = 0;
98                     },
99                 None => ()
100             }
101
102             let n = (self[i] as u32) << 16 |
103                     (self[i + 1] as u32) << 8 |
104                     (self[i + 2] as u32);
105
106             // This 24-bit number gets separated into four 6-bit numbers.
107             s.push_char(chars[(n >> 18) & 63]);
108             s.push_char(chars[(n >> 12) & 63]);
109             s.push_char(chars[(n >> 6 ) & 63]);
110             s.push_char(chars[n & 63]);
111
112             cur_length += 4;
113             i += 3;
114         }
115
116         if len % 3 != 0 {
117             match config.line_length {
118                 Some(line_length) =>
119                     if cur_length >= line_length {
120                         s.push_str("\r\n");
121                     },
122                 None => ()
123             }
124         }
125
126         // Heh, would be cool if we knew this was exhaustive
127         // (the dream of bounded integer types)
128         match len % 3 {
129             0 => (),
130             1 => {
131                 let n = (self[i] as u32) << 16;
132                 s.push_char(chars[(n >> 18) & 63]);
133                 s.push_char(chars[(n >> 12) & 63]);
134                 if config.pad {
135                     s.push_str("==");
136                 }
137             }
138             2 => {
139                 let n = (self[i] as u32) << 16 |
140                     (self[i + 1u] as u32) << 8;
141                 s.push_char(chars[(n >> 18) & 63]);
142                 s.push_char(chars[(n >> 12) & 63]);
143                 s.push_char(chars[(n >> 6 ) & 63]);
144                 if config.pad {
145                     s.push_char('=');
146                 }
147             }
148             _ => fail!("Algebra is broken, please alert the math police")
149         }
150         s
151     }
152 }
153
154 impl<'self> ToBase64 for &'self str {
155     /**
156      * Convert any string (literal, `@`, `&`, or `~`) to base64 encoding.
157      *
158      *
159      * # Example
160      *
161      * ~~~ {.rust}
162      * extern mod extra;
163      * use extra::base64::{ToBase64, standard};
164      *
165      * fn main () {
166      *     let str = "Hello, World".to_base64(standard);
167      *     printfln!("%s", str);
168      * }
169      * ~~~
170      *
171      */
172     fn to_base64(&self, config: Config) -> ~str {
173         self.as_bytes().to_base64(config)
174     }
175 }
176
177 /// A trait for converting from base64 encoded values.
178 pub trait FromBase64 {
179     /// Converts the value of `self`, interpreted as base64 encoded data, into
180     /// an owned vector of bytes, returning the vector.
181     fn from_base64(&self) -> Result<~[u8], ~str>;
182 }
183
184 impl<'self> FromBase64 for &'self [u8] {
185     /**
186      * Convert base64 `u8` vector into u8 byte values.
187      * Every 4 encoded characters is converted into 3 octets, modulo padding.
188      *
189      * # Example
190      *
191      * ~~~ {.rust}
192      * extern mod extra;
193      * use extra::base64::{ToBase64, FromBase64, standard};
194      *
195      * fn main () {
196      *     let str = [52,32].to_base64(standard);
197      *     printfln!("%s", str);
198      *     let bytes = str.from_base64();
199      *     printfln!("%?", bytes);
200      * }
201      * ~~~
202      */
203     fn from_base64(&self) -> Result<~[u8], ~str> {
204         let mut r = ~[];
205         let mut buf: u32 = 0;
206         let mut modulus = 0;
207
208         let mut it = self.iter();
209         for &byte in it {
210             let ch = byte as char;
211             let val = byte as u32;
212
213             match ch {
214                 'A'..'Z' => buf |= val - 0x41,
215                 'a'..'z' => buf |= val - 0x47,
216                 '0'..'9' => buf |= val + 0x04,
217                 '+'|'-' => buf |= 0x3E,
218                 '/'|'_' => buf |= 0x3F,
219                 '\r'|'\n' => loop,
220                 '=' => break,
221                 _ => return Err(~"Invalid Base64 character")
222             }
223
224             buf <<= 6;
225             modulus += 1;
226             if modulus == 4 {
227                 modulus = 0;
228                 r.push((buf >> 22) as u8);
229                 r.push((buf >> 14) as u8);
230                 r.push((buf >> 6 ) as u8);
231             }
232         }
233
234         if !it.all(|&byte| {byte as char == '='}) {
235             return Err(~"Invalid Base64 character");
236         }
237
238         match modulus {
239             2 => {
240                 r.push((buf >> 10) as u8);
241             }
242             3 => {
243                 r.push((buf >> 16) as u8);
244                 r.push((buf >> 8 ) as u8);
245             }
246             0 => (),
247             _ => return Err(~"Invalid Base64 length")
248         }
249
250         Ok(r)
251     }
252 }
253
254 impl<'self> FromBase64 for &'self str {
255     /**
256      * Convert any base64 encoded string (literal, `@`, `&`, or `~`)
257      * to the byte values it encodes.
258      *
259      * You can use the `from_bytes` function in `std::str`
260      * to turn a `[u8]` into a string with characters corresponding to those
261      * values.
262      *
263      * # Example
264      *
265      * This converts a string literal to base64 and back.
266      *
267      * ~~~ {.rust}
268      * extern mod extra;
269      * use extra::base64::{ToBase64, FromBase64, standard};
270      * use std::str;
271      *
272      * fn main () {
273      *     let hello_str = "Hello, World".to_base64(standard);
274      *     printfln!("%s", hello_str);
275      *     let bytes = hello_str.from_base64();
276      *     printfln!("%?", bytes);
277      *     let result_str = str::from_bytes(bytes);
278      *     printfln!("%s", result_str);
279      * }
280      * ~~~
281      */
282     fn from_base64(&self) -> Result<~[u8], ~str> {
283         self.as_bytes().from_base64()
284     }
285 }
286
287 #[cfg(test)]
288 mod test {
289     use test::BenchHarness;
290     use base64::*;
291
292     #[test]
293     fn test_to_base64_basic() {
294         assert_eq!("".to_base64(STANDARD), ~"");
295         assert_eq!("f".to_base64(STANDARD), ~"Zg==");
296         assert_eq!("fo".to_base64(STANDARD), ~"Zm8=");
297         assert_eq!("foo".to_base64(STANDARD), ~"Zm9v");
298         assert_eq!("foob".to_base64(STANDARD), ~"Zm9vYg==");
299         assert_eq!("fooba".to_base64(STANDARD), ~"Zm9vYmE=");
300         assert_eq!("foobar".to_base64(STANDARD), ~"Zm9vYmFy");
301     }
302
303     #[test]
304     fn test_to_base64_line_break() {
305         assert!(![0u8, 1000].to_base64(Config {line_length: None, ..STANDARD})
306                 .contains("\r\n"));
307         assert_eq!("foobar".to_base64(Config {line_length: Some(4), ..STANDARD}),
308                    ~"Zm9v\r\nYmFy");
309     }
310
311     #[test]
312     fn test_to_base64_padding() {
313         assert_eq!("f".to_base64(Config {pad: false, ..STANDARD}), ~"Zg");
314         assert_eq!("fo".to_base64(Config {pad: false, ..STANDARD}), ~"Zm8");
315     }
316
317     #[test]
318     fn test_to_base64_url_safe() {
319         assert_eq!([251, 255].to_base64(URL_SAFE), ~"-_8");
320         assert_eq!([251, 255].to_base64(STANDARD), ~"+/8=");
321     }
322
323     #[test]
324     fn test_from_base64_basic() {
325         assert_eq!("".from_base64().unwrap(), "".as_bytes().to_owned());
326         assert_eq!("Zg==".from_base64().unwrap(), "f".as_bytes().to_owned());
327         assert_eq!("Zm8=".from_base64().unwrap(), "fo".as_bytes().to_owned());
328         assert_eq!("Zm9v".from_base64().unwrap(), "foo".as_bytes().to_owned());
329         assert_eq!("Zm9vYg==".from_base64().unwrap(), "foob".as_bytes().to_owned());
330         assert_eq!("Zm9vYmE=".from_base64().unwrap(), "fooba".as_bytes().to_owned());
331         assert_eq!("Zm9vYmFy".from_base64().unwrap(), "foobar".as_bytes().to_owned());
332     }
333
334     #[test]
335     fn test_from_base64_newlines() {
336         assert_eq!("Zm9v\r\nYmFy".from_base64().unwrap(),
337                    "foobar".as_bytes().to_owned());
338     }
339
340     #[test]
341     fn test_from_base64_urlsafe() {
342         assert_eq!("-_8".from_base64().unwrap(), "+/8=".from_base64().unwrap());
343     }
344
345     #[test]
346     fn test_from_base64_invalid_char() {
347         assert!("Zm$=".from_base64().is_err())
348             assert!("Zg==$".from_base64().is_err());
349     }
350
351     #[test]
352     fn test_from_base64_invalid_padding() {
353         assert!("Z===".from_base64().is_err());
354     }
355
356     #[test]
357     fn test_base64_random() {
358         use std::rand::{task_rng, random, RngUtil};
359         use std::vec;
360
361         do 1000.times {
362             let v: ~[u8] = do vec::build |push| {
363                 do task_rng().gen_uint_range(1, 100).times {
364                     push(random());
365                 }
366             };
367             assert_eq!(v.to_base64(STANDARD).from_base64().unwrap(), v);
368         }
369     }
370
371     #[bench]
372     pub fn to_base64(bh: & mut BenchHarness) {
373         let s = "イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム \
374                  ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン";
375         do bh.iter {
376             s.to_base64(STANDARD);
377         }
378         bh.bytes = s.len() as u64;
379     }
380
381     #[bench]
382     pub fn from_base64(bh: & mut BenchHarness) {
383         let s = "イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム \
384                  ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン";
385         let b = s.to_base64(STANDARD);
386         do bh.iter {
387             b.from_base64();
388         }
389         bh.bytes = b.len() as u64;
390     }
391
392 }