index.js 9.0 KB

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