1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//! Decode a BinJS to a text source.

extern crate binjs;
extern crate clap;
extern crate env_logger;

use binjs::source::Shift;
use binjs::specialized::es6::io::Decoder;

use std::fs::{self, File};
use std::io::*;
use std::thread;

use clap::*;

macro_rules! progress {
    ($quiet:expr, $($args:tt)*) => {
        if !$quiet {
            println!($($args)*);
        }
    }
}

// Command line options.
struct Options<'a> {
    /// True if --print-json is specified.
    print_json: bool,

    /// The OUTPUT path, or None if not specified.
    dest_path: Option<&'a str>,

    /// The format used to decode.
    ///
    /// The decoder will not attempt to sniff the format used.
    format: binjs::io::Format,
}

fn main() {
    thread::Builder::new()
        .name("large stack dedicated thread".to_string())
        .stack_size(20 * 1024 * 1024)
        .spawn(|| {
            main_aux();
        })
        .expect("Could not launch dedicated thread")
        .join()
        .expect("Error in dedicated thread");
}

fn main_aux() {
    env_logger::init();

    let matches = App::new("BinJS decoder")
        .author("David Teller, <dteller@mozilla.com>")
        .about("Decode a JavaScript BinJS source to a JavaScript text source.")
        .args(&[
            Arg::with_name("INPUT").help(
                "Input file to use. Must be a BinJS source file. If not specified, stdin is used",
            ),
            Arg::with_name("OUTPUT")
                .help("Output file to use. Will be overwritten. If not specified, stdout is used"),
            Arg::with_name("dump")
                .long("dump")
                .takes_value(false)
                .help("If specified, dump a JSON version of the AST."),
            Arg::with_name("quiet")
                .long("quiet")
                .short("q")
                .help("Do not print progress"),
            Arg::with_name("print-json")
                .long("print-json")
                .help("Print JSON of parse tree"),
        ])
        .subcommand(binjs::io::Format::subcommand())
        .get_matches();

    // Prepare grammar (used for entropy).
    let spec = binjs::generic::es6::Library::spec();

    // Common options.
    let source_path = matches.value_of("INPUT");
    let dest_path = matches.value_of("OUTPUT");
    let quiet = matches.is_present("quiet") || dest_path.is_none();

    // Format options.
    let format =
        binjs::io::Format::from_matches(&spec, &matches).expect("Could not parse encoding format");
    progress!(quiet, "Using format: {}", format.name());

    // Setup.
    let mut options = Options {
        print_json: matches.is_present("print-json"),
        dest_path,
        format,
    };

    progress!(quiet, "Reading.");
    let mut tree = match source_path {
        Some(path) => parse_tree(
            &|| BufReader::new(File::open(path).expect("Could not open source")),
            &mut options,
        ),
        None => {
            let mut buffer = Vec::new();
            stdin()
                .read_to_end(&mut buffer)
                .expect("Failed to read from stdin");

            parse_tree(&|| Cursor::new(&buffer), &mut options)
        }
    };

    if options.print_json {
        progress!(quiet, "Printing to screen...");
        serde_json::to_writer_pretty(std::io::stdout(), &tree).unwrap();
        println!();
    }

    let cleaner = binjs::specialized::es6::Cleanup {
        scoped_dictionaries: true,
        ..Default::default()
    };
    cleaner.cleanup(&mut tree);

    progress!(quiet, "Pretty-printing");
    let printer = Shift::try_new().expect("Could not launch Shift");
    let source = printer.to_source(&tree).expect("Could not pretty-print");

    progress!(quiet, "Writing.");
    match options.dest_path {
        Some(path) => {
            fs::write(path, source).expect("Could not write destination file");
        }
        None => {
            stdout()
                .write(source.as_bytes())
                .expect("Could not write destination file");
        }
    }
}

fn parse_tree<R: Read + Seek>(
    get_stream: &dyn Fn() -> R,
    options: &mut Options,
) -> binjs::specialized::es6::ast::Script {
    let decoder = Decoder::new();
    decoder
        .decode(&mut options.format, get_stream())
        .expect("Could not decode")
}