From 56f4c1e2736e1097fc1ee104561c287214fd6749 Mon Sep 17 00:00:00 2001 From: Jordy Dickinson Date: Fri, 20 Dec 2013 14:34:45 -0500 Subject: [PATCH] Implement base64, resolves issue #42 --- Makefile | 1 + README.md | 1 - base64/base64.rs | 172 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 base64/base64.rs diff --git a/Makefile b/Makefile index 9d8d17163..be34ac58c 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ RMFLAGS := # Output names EXES := \ + base64 \ cat \ dirname \ echo \ diff --git a/README.md b/README.md index 759b5d1cb..9de76c4a1 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,6 @@ The steps above imply that, before starting to work on a utility, you should sea To do ----- -- base64 - basename - chcon - chgrp diff --git a/base64/base64.rs b/base64/base64.rs new file mode 100644 index 000000000..d27e8e84b --- /dev/null +++ b/base64/base64.rs @@ -0,0 +1,172 @@ +#[link(name="base64", vers="1.0.0", author="Jordy Dickinson")] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Jordy Dickinson + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +extern mod extra; + +use std::char; +use std::io::{File, stdin, stdout}; +use std::os; +use std::str; + +use extra::base64; +use extra::base64::{FromBase64, ToBase64}; + +fn main() { + let mut conf = Conf::new(os::args()); + + match conf.mode { + Decode => decode(&mut conf), + Encode => encode(&mut conf), + Help => help(&conf), + Version => version() + } +} + +fn decode(conf: &mut Conf) { + let mut to_decode = str::from_utf8_owned(conf.input_file.read_to_end()); + + to_decode = to_decode.replace("\n", ""); + + if conf.ignore_garbage { + let standard_chars = + bytes!("ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "abcdefghijklmnopqrstuvwxyz", + "0123456789+/").map(|b| char::from_u32(*b as u32).unwrap()); + + to_decode = to_decode + .trim_chars(&|c| !standard_chars.contains(&c)) + .to_owned(); + } + + match to_decode.from_base64() { + Ok(bytes) => { + let mut out = stdout(); + + out.write(bytes); + out.flush(); + } + Err(s) => fail!(s) + } +} + +fn encode(conf: &mut Conf) { + let b64_conf = base64::Config { + char_set: base64::Standard, + pad: true, + line_length: match conf.line_wrap { + 0 => None, + _ => Some(conf.line_wrap) + } + }; + let to_encode = conf.input_file.read_to_end(); + let mut encoded = to_encode.to_base64(b64_conf); + + // To my knowledge, RFC 3548 does not specify which line endings to use. It + // seems that rust's base64 algorithm uses CRLF as prescribed by RFC 2045. + // However, since GNU base64 outputs only LF (presumably because that is + // the standard UNIX line ending), we strip CRs from the output to maintain + // compatibility. + encoded = encoded.replace("\r", ""); + + println(encoded); +} + +fn help(conf: &Conf) { + println!("Usage: {:s} [OPTION]... [FILE]", conf.progname); + println(""); + println(conf.usage); + + let msg = ~"With no FILE, or when FILE is -, read standard input.\n\n" + + "The data are encoded as described for the base64 alphabet in RFC " + + "3548. When\ndecoding, the input may contain newlines in addition " + + "to the bytes of the formal\nbase64 alphabet. Use --ignore-garbage " + + "to attempt to recover from any other\nnon-alphabet bytes in the " + + "encoded stream."; + + println(msg); +} + +fn version() { + println("base64 1.0.0"); +} + +struct Conf { + progname: ~str, + usage: ~str, + mode: Mode, + ignore_garbage: bool, + line_wrap: uint, + input_file: ~Reader +} + +impl Conf { + fn new(args: &[~str]) -> Conf { + // The use statement is here rather than at the top of the file so that + // the reader is made directly aware that we're using getopts::groups, + // and not just getopts. Also some names are somewhat vague taken out + // of context (i.e., "usage"). + use extra::getopts::groups::{ + getopts, + optflag, + optopt, + usage + }; + + let opts = ~[ + optflag("d", "decode", "decode data"), + optflag("i", "ignore-garbage", + "when decoding, ignore non-alphabetic characters"), + optopt("w", "wrap", + "wrap encoded lines after COLS character (default 76, 0 to disable" + + " wrapping)", "COLS"), + optflag("h", "help", "display this help text and exit"), + optflag("V", "version", "output version information and exit") + ]; + // TODO: The user should get a clean error message in the case that + // unwrap() fails. + let matches = getopts(args.tail(), opts).unwrap(); + + Conf { + progname: args[0].clone(), + usage: usage("Base64 encode or decode FILE, or standard input, to " + + "standard output.", opts), + mode: if matches.opt_present("help") { + Help + } else if matches.opt_present("version") { + Version + } else if matches.opt_present("decode") { + Decode + } else { + Encode + }, + ignore_garbage: matches.opt_present("ignore-garbage"), + line_wrap: match matches.opt_str("wrap") { + Some(s) => from_str(s).unwrap(), + None => 76 + }, + input_file: if matches.free.is_empty() + || matches.free[0] == ~"-" { + ~stdin() as ~Reader + } else { + let path = Path::new(matches.free[0]); + ~File::open(&path) as ~Reader + } + } + } +} + +enum Mode { + Decode, + Encode, + Help, + Version +} +