index.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. #!/usr/bin/env node
  2. const program = require('commander')
  3. const path = require('path')
  4. const chalk = require('chalk')
  5. const version = require('./util/version_compare')
  6. const iPhoneBackup = require('./util/iphone_backup.js').iPhoneBackup
  7. var base = path.join(process.env.HOME, '/Library/Application Support/MobileSync/Backup/')
  8. var reportTypes = {
  9. 'apps': require('./reports/apps'),
  10. 'calls': require('./reports/calls'),
  11. 'conversations': require('./reports/conversations'),
  12. 'list': require('./reports/list'),
  13. 'manifest': require('./reports/manifest'),
  14. 'messages': require('./reports/messages'),
  15. 'notes': require('./reports/notes'),
  16. 'oldnotes': require('./reports/oldnotes'),
  17. 'photolocations': require('./reports/photolocations'),
  18. 'voicemail-files': require('./reports/voicemail-files'),
  19. 'voicemail': require('./reports/voicemail'),
  20. 'webhistory': require('./reports/webhistory'),
  21. 'wifi': require('./reports/wifi')
  22. }
  23. var formatters = {
  24. 'json': require('./formatters/json'),
  25. 'table': require('./formatters/table'),
  26. 'raw': require('./formatters/raw-json'),
  27. 'raw-json': require('./formatters/raw-json'),
  28. 'csv': require('./formatters/csv'),
  29. 'raw-csv': require('./formatters/raw-csv'),
  30. }
  31. program
  32. .version('3.0.0')
  33. .option('-l, --list', 'List Backups')
  34. .option(`-b, --backup <backup>`, 'Backup ID')
  35. .option(`-d, --dir <directory>`, `Backup Directory (default: ${base})`)
  36. .option('-r, --report <report_type>', 'Select a report type. see below for a full list.')
  37. .option('-i, --id <id>', 'Specify an ID for filtering certain reports')
  38. .option('-f, --formatter <type>', 'Specify output format. default: table')
  39. .option(`-e, --extract <dir>`, 'Extract data for commands. supported by: voicemail-files, manifest')
  40. .option('-o, --report-output <path>', 'Specify an output directory for files to be written to.')
  41. .option(`-v, --verbose`, 'Verbose debugging output')
  42. .option(` --filter <filter>`, 'Filter output for individual reports. See the README for usage.')
  43. .option(' --join-reports', 'Join JSON reports together. (available for -f json or -f raw only!)')
  44. .option(` --no-color`, 'Disable colorized output')
  45. .option(` --dump`, 'alias for "--formatter raw"')
  46. program.on('--help', function () {
  47. console.log('')
  48. console.log('Supported Report Types:')
  49. // Generate a list of report types.
  50. for (var i in reportTypes) {
  51. var r = reportTypes[i]
  52. if (program.isTTY) {
  53. console.log(' ', r.name, (r.supportedVersions ? '(iOS ' + r.supportedVersions + ')' : ' ') + '-', r.description)
  54. } else {
  55. console.log(' ', chalk.green(r.name), (r.supportedVersions ? chalk.gray('(iOS ' + r.supportedVersions + ') ') : '') + '-', r.description)
  56. }
  57. }
  58. console.log('')
  59. console.log("If you're interested to know how this works, check out my post:")
  60. console.log('https://www.richinfante.com/2017/3/16/reverse-engineering-the-ios-backup')
  61. console.log('')
  62. console.log('Issue tracker:')
  63. console.log('https://github.com/richinfante/iphonebackuptools/issues')
  64. console.log('')
  65. })
  66. // Parse argv.
  67. program.parse(process.argv)
  68. // Global verbose output flag.
  69. // This is bad
  70. global.verbose = program.verbose
  71. // Save the formatter
  72. program.formatter = formatters[program.formatter] || formatters.table
  73. // Legacy support for `--dump` flag.
  74. if (program.dump) {
  75. program.formatter = formatters.raw
  76. }
  77. // Disable color for non-ttys.
  78. if (!process.stdout.isTTY) { program.color = false }
  79. // Find the base
  80. base = program.dir || base
  81. if (program.verbose) console.log('Using source:', base)
  82. // Run the main function
  83. main()
  84. async function main() {
  85. if (program.list) {
  86. // Run the list report standalone
  87. let result = await new Promise((resolve, reject) => {
  88. reportTypes.list.func(program, base, resolve, reject)
  89. })
  90. } else if (program.report) {
  91. var reportContents = []
  92. // Turn the report argument into an array of report type names
  93. var selectedTypes = program.report
  94. .split(',')
  95. .map(el => el.trim())
  96. .filter(el => el != '')
  97. // Add all types if type is 'all'
  98. if (program.report == 'all') {
  99. selectedTypes = []
  100. for (var key in reportTypes) {
  101. if (reportTypes[key].requiresInteractivity === true) {
  102. continue
  103. }
  104. selectedTypes.push(key)
  105. }
  106. }
  107. for(var reportName of selectedTypes) {
  108. // If the report is valid
  109. if (reportTypes[reportName]) {
  110. var report = reportTypes[reportName]
  111. if(selectedTypes.length > 1 && !report.usesPromises) {
  112. console.log('Warning: report that does not utilize promises in multi-request.')
  113. console.log('Warning: this may not work.')
  114. }
  115. // Check if there's a backup specified and one is required.
  116. if (report.requiresBackup) {
  117. if (!program.backup) {
  118. console.log('use -b or --backup <id> to specify backup.')
  119. process.exit(1)
  120. }
  121. }
  122. if (report.func) {
  123. var report = await runSingleReport(report, program)
  124. reportContents.push({
  125. name: reportName,
  126. contents: report
  127. })
  128. } else if (report.functions) {
  129. var report = await runSwitchedReport(report, program)
  130. reportContents.push({
  131. name: reportName,
  132. contents: report
  133. })
  134. }
  135. } else {
  136. console.log('')
  137. console.log(' [!] Unknown Option type:', reportName)
  138. console.log(' [!] It\'s possible this tool is out-of date.')
  139. console.log(' https://github.com/richinfante/iphonebackuptools/issues')
  140. console.log('')
  141. program.outputHelp()
  142. }
  143. }
  144. program.formatter.finalReport(reportContents, program)
  145. } else {
  146. program.outputHelp()
  147. }
  148. }
  149. async function runSwitchedReport(report, program) {
  150. try {
  151. // New type of reports
  152. var backup = iPhoneBackup.fromID(program.backup, base)
  153. var flag = false
  154. var value
  155. // Check for a compatible reporting tool.
  156. for (var key in report.functions) {
  157. if (version.versionCheck(backup.iOSVersion, key)) {
  158. if(!report.usesPromises) {
  159. if(program.verbose) console.log('using synchronous call.')
  160. value = report.functions[key](program, backup)
  161. } else {
  162. // Create a promise to resolve this function
  163. async function createPromise() {
  164. if(program.verbose) console.log('resolving using promises.')
  165. return new Promise((resolve, reject) => {
  166. report.functions[key](program, backup, resolve, reject)
  167. })
  168. }
  169. // Use promises to resolve synchronously
  170. value = await createPromise()
  171. }
  172. flag = true
  173. break
  174. }
  175. }
  176. if (!flag) {
  177. console.log('[!] The report generator "', program.report,'" does not support iOS', backup.iOSVersion)
  178. console.log('')
  179. console.log(' If you think it should, file an issue here:')
  180. console.log(' https://github.com/richinfante/iphonebackuptools/issues')
  181. console.log('')
  182. process.exit(1)
  183. }
  184. return value
  185. } catch (e) {
  186. console.log('[!] Encountered an error', e)
  187. }
  188. }
  189. async function runSingleReport(report, program) {
  190. async function runReport(backup, base) {
  191. if(!report.usesPromises) {
  192. if(program.verbose) console.log('using synchronous call.')
  193. // Old-style non-promise based report.
  194. if (report.requiresBackup) {
  195. return report.func(program, backup)
  196. } else {
  197. return report.func(program, base)
  198. }
  199. } else {
  200. // Create a promise to resolve this function
  201. async function createPromise() {
  202. if(program.verbose) console.log('resolving using promises.')
  203. return new Promise((resolve, reject) => {
  204. if (report.requiresBackup) {
  205. report.func(program, backup, resolve, reject)
  206. } else {
  207. report.func(program, base, resolve, reject)
  208. }
  209. })
  210. }
  211. // Use promises to resolve synchronously
  212. return await createPromise()
  213. }
  214. }
  215. try {
  216. // New type of reports
  217. var backup = iPhoneBackup.fromID(program.backup, base)
  218. if (report.supportedVersions !== undefined) {
  219. if (version.versionCheck(backup.iOSVersion, report.supportedVersions)) {
  220. return await runReport(backup, base)
  221. } else {
  222. console.log('[!] The report generator "' + program.report + '" does not support iOS', backup.iOSVersion)
  223. console.log('')
  224. console.log(' If you think it should, file an issue here:')
  225. console.log(' https://github.com/richinfante/iphonebackuptools/issues')
  226. console.log('')
  227. process.exit(1)
  228. }
  229. } else {
  230. return await runReport(backup, base)
  231. }
  232. } catch (e) {
  233. console.log('[!] Encountered an error', e)
  234. }
  235. }