123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- #!/usr/bin/env node
- const program = require('commander')
- const chalk = require('chalk')
- const log = require('./util/log')
- const report = require('./reports')
- const matcher = require('./util/matcher')
- const Group = report.Group
- const core = require('./index')
- const packageJSON = require('../package.json')
- const { runSingleReport, runSwitchedReport } = require('./util/report_runner')
- var formatters = {
- 'json': require('./formatters/json'),
- 'table': require('./formatters/table'),
- 'raw': require('./formatters/raw-json'),
- 'raw-json': require('./formatters/raw-json'),
- 'csv': require('./formatters/csv'),
- 'raw-csv': require('./formatters/raw-csv')
- }
- process.on('unhandledRejection', (e) => {
- console.log('[cli.js] unhandled rejection', e)
- process.exit(1)
- })
- program
- .version(packageJSON.version)
- .option('-l, --list', 'List Backups')
- .option(`-b, --backup <backup>`, 'Backup ID')
- .option(`-d, --dir <directory>`, `Backup Directory (default: ${core.base})`)
- .option('-r, --report <report_type>', 'Select a report type. see below for a full list.')
- .option('-i, --id <id>', 'Specify an ID for filtering certain reports')
- .option('-f, --formatter <type>', 'Specify output format. default: table')
- .option(`-e, --extract <dir>`, 'Extract data for commands. supported by: voicemail-files, manifest, addressbook')
- .option('-o, --output <path>', 'Specify an output directory for files to be written to.')
- .option(`-v, --verbose`, 'Verbose debugging output')
- .option(` --plugins <plugins>`, 'List of pluging modules to use')
- .option(` --filter <filter...>`, 'Filters output for individual reports.')
- .option(` --regex-filter <filter...>`, 'Filters output for individual reports using a regular expression.')
- .option(' --join-reports', 'Join JSON reports together. (available for -f json or -f raw only!)')
- .option(` --no-color`, 'Disable colorized output')
- .option(` --dump`, 'alias for "--formatter raw"')
- .option(` --quiet`, 'quiet all messages, except for errors and raw output')
- .option(` --available`, 'output a list of available reports')
- program.on('--help', function () {
- console.log('')
- console.log(`Version: ${packageJSON.version}`)
- console.log('')
- console.log('Run ibackuptool --available for a listing of report types.')
- console.log('')
- console.log("If you're interested to know how this works, check out my post:")
- console.log('https://www.richinfante.com/2017/3/16/reverse-engineering-the-ios-backup')
- console.log('')
- console.log('Issue tracker:')
- console.log('https://github.com/richinfante/iphonebackuptools/issues')
- console.log('')
- })
- function printReportList () {
- // Print a report group and recursively print children
- function printGroup (group, i, pn) {
- i = i || 0
- pn = pn || ''
- for (let [name, report] of Object.entries(group)) {
- // Ignore groups
- if (name === '__group') { continue }
- if (report instanceof Group || report.__group === true) {
- // console.log(`${' '.repeat(i * 2)}- ${pn}${name}`.padStart(i * 2))
- printGroup(report, i + 1, `${pn}${name}.`)
- } else {
- console.log(`- ${chalk.green(pn + name)}${report.deprecated ? chalk.gray(' [deprecated]') : ''}: ${report.description} `)
- }
- }
- }
- printGroup(core.modules)
- }
- process.on('unhandledRejection', (e) => {
- console.log('[index.js] unhandled rejection', e)
- process.exit(1)
- })
- // If we're the main module, run some things.
- if (require.main === module) {
- init()
- }
- // Initialize the tool.
- function init () {
- program.parse(process.argv)
- if (program.available) {
- printReportList()
- process.exit(0)
- }
- // Set the log level
- log.setVerbose(program.quiet ? 0 : (program.verbose ? 2 : 1))
- // Parse plugins
- program.plugins = program.plugins || process.env.IBACKUPTOOL_PLUGINS || ''
- program.plugins = program.plugins.split(',')
- .map(name => name.trim())
- .filter(name => name !== '')
- .map(path => require(path))
- // Register witht the core module
- core.registerModule(program.plugins)
- log.verbose('Plugins:', program.plugins)
- // Save the formatter
- program.formatter = formatters[program.formatter] || formatters.table
- // Legacy support for `--dump` flag.
- if (program.dump) {
- program.formatter = formatters.raw
- }
- // Disable color for non-ttys.
- if (!process.stdout.isTTY) { program.color = false }
- // Find the base if it is set
- if (program.dir) {
- core.base = program.dir
- }
- log.verbose('Base Directory:', core.base)
- main().then(() => {})
- }
- /**
- * Main CLI function
- */
- async function main () {
- // Legacy support for --list (-l) flag
- if (program.list) {
- program.report = 'backups.list'
- }
- log.verbose('Top-level modules', Object.keys(core.modules))
- if (program.report) {
- var reportContents = []
- // Turn the report argument into an array of report type names
- var selectedTypes = program.report
- .split(',')
- .map(el => el.trim())
- .filter(el => el !== '')
- let selectedReports = []
- // Match an array of reports.
- for (let query of selectedTypes) {
- selectedReports = [
- ...selectedReports,
- ...matcher(core.modules, query, (el) => !(el instanceof Group || el.__group === true))
- ]
- }
- let set = new Set(selectedReports)
- log.verbose('selected set', set)
- // If the set's size is 0, throw an error.
- if (set.size === 0) {
- log.error(`Couldn't run reports specified by: '${program.report}'.`)
- log.error(`No matching reports were found.`)
- process.exit(1)
- }
- // Iterate over each item in the set.
- for (let report of set.values()) {
- if (report.version && report.version >= 3) {
- try {
- log.begin('run', report.name)
- var filters = program.filter || [];
- for (var filter of program.regexFilter || [])
- filters.push(new RegExp(filter));
- // Create some parameters to send by default.
- let params = {
- backup: program.backup,
- extract: program.extract,
- filter: filters,
- id: program.id,
- raw: !!program.formatter.isRaw
- }
- // Run a v3 report.
- let contents = await core.runReport(report, params)
- // Possibly symbolicate
- contents = await core.compileReport(report, contents, params)
- // Format the v3 report's result.
- let formattedContent = program.formatter.format(contents, {
- program
- })
- // Push onto the list to be compiled.
- reportContents.push({
- name: report.name,
- contents: formattedContent
- })
- log.end()
- } catch (e) {
- log.end()
- log.error(`Couldn't run '${report.name}'.`)
- log.error(e)
- }
- } else {
- // Older reports still work for file and screen output
- // They just may not be as compatible
- // They cannot be called from the module's api.
- if (selectedReports.length > 1 && !report.usesPromises) {
- log.warning('the report', report.name, 'does not utilize promises.')
- log.warning('this may not work')
- }
- log.begin('run', report.name)
- // Check if there's a backup specified and one is required.
- if (report.requiresBackup) {
- if (!program.backup) {
- log.error('use -b or --backup <id> to specify backup.')
- process.exit(1)
- }
- }
- try {
- if (report.func) {
- let contents = await runSingleReport(report, program)
- if (contents == null) { log.end(); continue }
- reportContents.push({
- name: report.name,
- contents: contents
- })
- } else if (report.functions) {
- let contents = await runSwitchedReport(report, program)
- if (contents == null) { log.end(); continue }
- reportContents.push({
- name: report.name,
- contents: contents
- })
- }
- } catch (e) {
- log.error(`Couldn't run '${report.name}'.`)
- log.error(e)
- }
- log.end()
- }
- }
- program.formatter.finalReport(reportContents, program)
- } else {
- program.outputHelp()
- }
- }
|