iphone_backup.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  1. const log = require('./log')
  2. const path = require('path')
  3. const sqlite3 = require('sqlite3')
  4. const fs = require('fs')
  5. const plist = require('./plist')
  6. const os = require('os')
  7. // Cookie Parser
  8. const cookieParser = require('./cookies.js')
  9. // Normalize mac addresses in wifi output
  10. const macParse = require('./mac_address_parse')
  11. // Derive filenames based on domain + file path
  12. const fileHash = require('./backup_filehash')
  13. // Manifest.mbdb parser
  14. const manifestMBDBParse = require('./manifest_mbdb_parse')
  15. // Pushstore plist parser
  16. const pushstoreParse = require('./pushstore_parse')
  17. const databases = {
  18. SMS: fileHash('Library/SMS/sms.db'),
  19. Contacts: fileHash('Library/AddressBook/AddressBook.sqlitedb'),
  20. Calendar: fileHash('Library/Calendar/Calendar.sqlitedb'),
  21. Reminders: fileHash('Library/Calendar/Calendar.sqlitedb'),
  22. Notes: fileHash('Library/Notes/notes.sqlite'),
  23. Notes2: fileHash('NoteStore.sqlite', 'AppDomainGroup-group.com.apple.notes'),
  24. AddressBook: fileHash('Library/AddressBook/AddressBook.sqlitedb'),
  25. AddressBookImages: fileHash('Library/AddressBook/AddressBookImages.sqlitedb'),
  26. 'Cookies.binarycookies': '69b1865768101bacde5b77ccc44445cea9ce1261',
  27. Calls: '2b2b0084a1bc3a5ac8c27afdf14afb42c61a19ca',
  28. Calls2: fileHash('Library/CallHistoryDB/CallHistory.storedata'),
  29. Locations: fileHash('Library/Caches/locationd/consolidated.db', 'RootDomain'),
  30. WebHistory: fileHash('Library/Safari/History.db', 'AppDomain-com.apple.mobilesafari'),
  31. Photos: fileHash('Media/PhotoData/Photos.sqlite', 'CameraRollDomain'),
  32. WiFi: fileHash('SystemConfiguration/com.apple.wifi.plist', 'SystemPreferencesDomain'),
  33. Voicemail: fileHash('Library/Voicemail/voicemail.db'),
  34. SafariBookmarks: fileHash('Library/Safari/Bookmarks.db')
  35. }
  36. var cache = {}
  37. class IPhoneBackup {
  38. constructor (id, status, info, manifest, base) {
  39. log.warning('v3 reporting API is deprecated, this report may need to be updated to ensure stability with all iOS versions')
  40. log.warning('https://github.com/richinfante/iphonebackuptools/wiki/V4-API-Migration-Notes')
  41. this.id = id
  42. this.status = status
  43. this.info = info
  44. this.manifest = manifest
  45. this.base = base
  46. }
  47. // Open a backup with a specified ID
  48. // base is optional and will be computed if not used.
  49. static fromID (id, base) {
  50. id = id || ''
  51. // Get the path of the folder.
  52. if (base) {
  53. base = path.join(base, id)
  54. } else {
  55. base = path.join(os.homedir(), '/Library/Application Support/MobileSync/Backup/', id)
  56. }
  57. // Parse manifest plist files
  58. try {
  59. log.verbose('parsing status', base)
  60. var status = plist.parseFile(path.join(base, 'Status.plist'))
  61. } catch (e) {
  62. log.error('Cannot open Status.plist', e)
  63. }
  64. try {
  65. log.verbose('parsing manifest', base)
  66. var manifest = plist.parseFile(path.join(base, 'Manifest.plist'))
  67. } catch (e) {
  68. log.error('Cannot open Manifest.plist', e)
  69. }
  70. try {
  71. log.verbose('parsing status', base)
  72. var info = plist.parseFile(path.join(base, 'Info.plist'))
  73. } catch (e) {
  74. log.error('Cannot open Info.plist', e)
  75. }
  76. return new IPhoneBackup(id, status, info, manifest, base)
  77. }
  78. get iOSVersion () {
  79. return this.manifest.Lockdown.ProductVersion
  80. }
  81. getFileName (fileID, isAbsoulte) {
  82. isAbsoulte = isAbsoulte || false
  83. // const base = path.join(process.env.HOME, '/Library/Application Support/MobileSync/Backup/', this.id)
  84. // Return v2 filename
  85. if (this.status.Version < 3 || isAbsoulte) {
  86. return path.join(this.base, fileID)
  87. } else {
  88. // v3 has folders
  89. return path.join(this.base, fileID.substr(0, 2), fileID)
  90. }
  91. }
  92. openDatabase (fileID, isAbsoulte) {
  93. return new Promise((resolve, reject) => {
  94. isAbsoulte = isAbsoulte || false
  95. // Get the backup folder
  96. // Return v2 filename
  97. if (this.status.Version < 3 || isAbsoulte) {
  98. let db = new sqlite3.Database(path.join(this.base, fileID), sqlite3.OPEN_READONLY, (err) => {
  99. if (err) {
  100. return reject(err)
  101. }
  102. resolve(db)
  103. })
  104. } else {
  105. // v3 has folders
  106. let db = new sqlite3.Database(path.join(this.base, fileID.substr(0, 2), fileID), sqlite3.OPEN_READONLY, (err) => {
  107. if (err) {
  108. return reject(err)
  109. }
  110. resolve(db)
  111. })
  112. }
  113. })
  114. }
  115. /// This is deprecated. Use openDatabase Instead.
  116. getDatabase (fileID, isAbsoulte) {
  117. isAbsoulte = isAbsoulte || false
  118. // Get the backup folder
  119. // Return v2 filename
  120. if (this.status.Version < 3 || isAbsoulte) {
  121. return new sqlite3.Database(path.join(this.base, fileID), sqlite3.OPEN_READONLY, (err) => {
  122. if (err) {
  123. log.error('PANIC::', err)
  124. process.exit(1)
  125. }
  126. })
  127. } else {
  128. // v3 has folders
  129. return new sqlite3.Database(path.join(this.base, fileID.substr(0, 2), fileID), sqlite3.OPEN_READONLY, (err) => {
  130. if (err) {
  131. log.error('PANIC::', err)
  132. process.exit(1)
  133. }
  134. })
  135. }
  136. }
  137. queryDatabase (databaseID, sql) {
  138. return new Promise((resolve, reject) => {
  139. var messagedb = this.getDatabase(databaseID)
  140. messagedb.all(sql, async function (err, rows) {
  141. if (err) reject(err)
  142. resolve(rows)
  143. })
  144. })
  145. }
  146. getName (messageDest) {
  147. return new Promise((resolve, reject) => {
  148. if (messageDest.indexOf('@') === -1) {
  149. messageDest = messageDest.replace(/[\s+\-()]*/g, '')
  150. if (messageDest.length === 11 && messageDest[0] === '1') {
  151. messageDest = messageDest.substring(1)
  152. }
  153. }
  154. if (cache[messageDest] !== undefined) {
  155. return resolve(cache[messageDest])
  156. }
  157. var contactdb = this.getDatabase(databases.Contacts)
  158. contactdb.get(`SELECT
  159. c0First as first,
  160. c1Last as last,
  161. c2Middle as middle,
  162. c15Phone as phones
  163. from ABPersonFullTextSearch_content WHERE c15Phone like '%${messageDest}%'`,
  164. (err, row) => {
  165. if (err) return resolve()
  166. if (!row) return resolve()
  167. var result = {
  168. name: [row.first, row.middle, row.last].filter(el => el != null).join(' '),
  169. phones: row.phones.split(' '),
  170. query: messageDest
  171. }
  172. if (row) cache[messageDest] = result
  173. resolve(result)
  174. })
  175. })
  176. }
  177. getMessagesiOS9 (chatId) {
  178. var backup = this
  179. return new Promise((resolve, reject) => {
  180. var messagedb = this.getDatabase(databases.SMS)
  181. messagedb.all(`
  182. SELECT
  183. message.*,
  184. handle.id as sender_name,
  185. datetime(date + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING
  186. FROM chat_message_join
  187. INNER JOIN message
  188. ON message.rowid = chat_message_join.message_id
  189. INNER JOIN handle
  190. ON handle.rowid = message.handle_id
  191. WHERE chat_message_join.chat_id = ?`, [parseInt(chatId)],
  192. async function (err, chats) {
  193. if (err) return reject(err)
  194. chats = chats || []
  195. // Compute the user's name
  196. for (var i in chats) {
  197. var el = chats[i]
  198. el.x_sender = el.is_from_me ? 'Me' : el.sender_name
  199. if (!el.is_from_me) {
  200. var contact = await backup.getName(el.sender_name)
  201. if (contact) {
  202. el.x_sender = `${contact.name} <${contact.query}>`
  203. }
  204. }
  205. }
  206. resolve(chats)
  207. })
  208. })
  209. }
  210. getMessagesiOS10iOS11 (chatId) {
  211. var backup = this
  212. return new Promise((resolve, reject) => {
  213. var messagedb = this.getDatabase(databases.SMS)
  214. messagedb.all(`
  215. SELECT
  216. message.*,
  217. handle.id as sender_name,
  218. datetime(date / 1000000000 + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING
  219. FROM chat_message_join
  220. INNER JOIN message
  221. ON message.rowid = chat_message_join.message_id
  222. INNER JOIN handle
  223. ON handle.rowid = message.handle_id
  224. WHERE chat_message_join.chat_id = ?`, [parseInt(chatId)],
  225. async function (err, chats) {
  226. if (err) return reject(err)
  227. chats = chats || []
  228. // Compute the user's name
  229. for (var i in chats) {
  230. var el = chats[i]
  231. el.x_sender = el.is_from_me ? 'Me' : el.sender_name
  232. if (!el.is_from_me) {
  233. var contact = await backup.getName(el.sender_name)
  234. if (contact) {
  235. el.x_sender = `${contact.name} <${contact.query}>`
  236. }
  237. }
  238. }
  239. resolve(chats)
  240. })
  241. })
  242. }
  243. getMessages (chatId) {
  244. if (parseInt(this.manifest.Lockdown.BuildVersion) <= 13) {
  245. return this.getMessagesiOS9(chatId)
  246. } else {
  247. return this.getMessagesiOS10iOS11(chatId)
  248. }
  249. }
  250. getMessageAttachments (messageId) {
  251. var backup = this
  252. return new Promise((resolve, reject) => {
  253. const messagedb = this.getDatabase(databases.SMS)
  254. const query = `
  255. SELECT
  256. attachment.*
  257. FROM message_attachment_join
  258. INNER JOIN attachment
  259. ON attachment.ROWID = message_attachment_join.attachment_id
  260. WHERE message_attachment_join.message_id = ${parseInt(messageId)}
  261. `
  262. messagedb.all(query,
  263. async function (err, attachments) {
  264. if (err) return reject(err)
  265. attachments = attachments || []
  266. resolve(attachments)
  267. })
  268. })
  269. }
  270. getConversationsiOS9 () {
  271. var backup = this
  272. return new Promise((resolve, reject) => {
  273. var messagedb = this.getDatabase(databases.SMS)
  274. messagedb.all(`SELECT * FROM chat ORDER BY ROWID ASC`, async function (err, rows) {
  275. if (err) return reject(err)
  276. rows = rows || []
  277. // We need to do some manual parsing of these records.
  278. // The timestamp information is stored in a binary blob named `properties`
  279. // Which is formatted as a binary PLIST.
  280. for (var el of rows) {
  281. if (el.properties) el.properties = plist.parseBuffer(el.properties)
  282. // Interestingly, some of these do not have dates attached.
  283. if (el.properties) {
  284. el.date = new Date(el.properties.CKChatWatermarkTime * 1000)
  285. } else {
  286. el.date = new Date(0)
  287. }
  288. var contact = await backup.getName(el.chat_identifier)
  289. if (contact) {
  290. el.display_name = `${contact.name} <${contact.query}>`
  291. }
  292. // Format as YY-MM-DD HH:MM:SS
  293. try {
  294. el.XFORMATTEDDATESTRING = el.date.toISOString()
  295. .split('T')
  296. .join(' ')
  297. .split('Z')
  298. .join(' ')
  299. .split('.')[0]
  300. .trim()
  301. } catch (e) {
  302. el.XFORMATTEDDATESTRING = ''
  303. }
  304. }
  305. // Sort by the date.
  306. rows = rows.sort(function (a, b) {
  307. return (a.date.getTime() || 0) - (b.date.getTime() || 0)
  308. })
  309. resolve(rows)
  310. })
  311. })
  312. }
  313. getConversationsiOS10iOS11 () {
  314. return new Promise((resolve, reject) => {
  315. var messagedb = this.getDatabase(databases.SMS)
  316. messagedb.all(`SELECT *, datetime(last_read_message_timestamp / 1000000000 + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING FROM chat ORDER BY last_read_message_timestamp ASC`, async function (err, rows) {
  317. if (err) return reject(err)
  318. rows = rows || []
  319. resolve(rows)
  320. })
  321. })
  322. }
  323. getConversations () {
  324. if (parseInt(this.manifest.Lockdown.BuildVersion) <= 14) {
  325. return this.getConversationsiOS9()
  326. } else {
  327. return this.getConversationsiOS10iOS11()
  328. }
  329. }
  330. getFileManifest () {
  331. return new Promise((resolve, reject) => {
  332. var messagedb = this.getDatabase('Manifest.db', true)
  333. messagedb.all('SELECT * from FILES', async function (err, rows) {
  334. if (err) reject(err)
  335. resolve(rows)
  336. })
  337. })
  338. }
  339. getOldFileManifest () {
  340. return new Promise((resolve, reject) => {
  341. let mbdbPath = this.getFileName('Manifest.mbdb', true)
  342. manifestMBDBParse.process(mbdbPath, resolve, reject)
  343. })
  344. }
  345. getOldNotes () {
  346. return new Promise((resolve, reject) => {
  347. var messagedb = this.getDatabase(databases.Notes)
  348. messagedb.all(`SELECT *, datetime(ZCREATIONDATE + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING from ZNOTE LEFT JOIN ZNOTEBODY ON ZBODY = ZNOTEBODY.Z_PK`, async function (err, rows) {
  349. if (err) reject(err)
  350. resolve(rows)
  351. })
  352. })
  353. }
  354. getNewNotesiOS9 () {
  355. return new Promise((resolve, reject) => {
  356. var messagedb = this.getDatabase(databases.Notes2)
  357. messagedb.all(`SELECT *, datetime(ZCREATIONDATE + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING FROM ZICCLOUDSYNCINGOBJECT`, async function (err, rows) {
  358. if (err) reject(err)
  359. resolve(rows)
  360. })
  361. })
  362. }
  363. getNewNotesiOS10iOS11 () {
  364. return new Promise((resolve, reject) => {
  365. var messagedb = this.getDatabase(databases.Notes2)
  366. messagedb.all(`SELECT *, datetime(ZCREATIONDATE + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING, datetime(ZCREATIONDATE1 + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING1 FROM ZICCLOUDSYNCINGOBJECT`, async function (err, rows) {
  367. if (err) reject(err)
  368. resolve(rows)
  369. })
  370. })
  371. }
  372. getNotes () {
  373. if (parseInt(this.manifest.Lockdown.BuildVersion) <= 13) {
  374. // Legacy iOS 9 support
  375. // May work for earlier but I haven't tested it
  376. return this.getNewNotesiOS9()
  377. } else {
  378. return this.getNewNotesiOS10iOS11()
  379. }
  380. }
  381. getWebHistory () {
  382. return new Promise((resolve, reject) => {
  383. var messagedb = this.getDatabase(databases.WebHistory)
  384. messagedb.all(`SELECT *, datetime(visit_time + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING from history_visits LEFT JOIN history_items ON history_items.ROWID = history_visits.history_item`, async function (err, rows) {
  385. if (err) reject(err)
  386. resolve(rows)
  387. })
  388. })
  389. }
  390. getPhotoLocationHistory () {
  391. return new Promise((resolve, reject) => {
  392. var messagedb = this.getDatabase(databases.Photos)
  393. messagedb.all(`SELECT ZDATECREATED, ZLATITUDE, ZLONGITUDE, ZFILENAME, datetime(ZDATECREATED + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING FROM ZGENERICASSET ORDER BY ZDATECREATED ASC`, async function (err, rows) {
  394. if (err) reject(err)
  395. resolve(rows)
  396. })
  397. })
  398. }
  399. getGeofencesList () {
  400. return new Promise((resolve, reject) => {
  401. var messagedb = this.getDatabase(databases.Locations)
  402. messagedb.all(`SELECT datetime(Timestamp + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING, Latitude, Longitude, Distance FROM Fences ORDER BY Timestamp ASC`, async function (err, rows) {
  403. if (err) reject(err)
  404. resolve(rows)
  405. })
  406. })
  407. }
  408. getCallsStatisticsiOS7 () {
  409. return new Promise((resolve, reject) => {
  410. var messagedb = this.getDatabase(databases.Calls)
  411. messagedb.all(`SELECT * from _SqliteDatabaseProperties`, async function (err, rows) {
  412. if (err) reject(err)
  413. resolve(rows)
  414. })
  415. })
  416. }
  417. getCallsStatistics () {
  418. return new Promise((resolve, reject) => {
  419. var messagedb = this.getDatabase(databases.Calls2)
  420. messagedb.all(`SELECT * from ZCALLDBPROPERTIES`, async function (err, rows) {
  421. if (err) reject(err)
  422. resolve(rows)
  423. })
  424. })
  425. }
  426. getCallsList () {
  427. if (parseInt(this.manifest.Lockdown.BuildVersion) <= 13) {
  428. // Legacy iOS 9 support
  429. // May work for earlier but I haven't tested it
  430. return this.getCallsListiOS7()
  431. } else {
  432. return this.getCallsListLater()
  433. }
  434. }
  435. getCallsListiOS7 () {
  436. return new Promise((resolve, reject) => {
  437. var messagedb = this.getDatabase(databases.Calls)
  438. messagedb.all(`SELECT
  439. ROWID as Z_PK,
  440. datetime(date, 'unixepoch') AS XFORMATTEDDATESTRING,
  441. answered as ZANSWERED,
  442. duration as ZDURATION,
  443. address as ZADDRESS,
  444. country_code as ZISO_COUNTRY_CODE,
  445. country_code as ZISO_COUNTRY_CODE,
  446. * from call ORDER BY date ASC`, async function (err, rows) {
  447. if (err) reject(err)
  448. resolve(rows)
  449. })
  450. })
  451. }
  452. getCallsListLater () {
  453. return new Promise((resolve, reject) => {
  454. var messagedb = this.getDatabase(databases.Calls2)
  455. messagedb.all(`SELECT *, datetime(ZDATE + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING from ZCALLRECORD ORDER BY ZDATE ASC`, async function (err, rows) {
  456. if (err) reject(err)
  457. resolve(rows)
  458. })
  459. })
  460. }
  461. getVoicemailsList () {
  462. return new Promise((resolve, reject) => {
  463. var messagedb = this.getDatabase(databases.Voicemail)
  464. messagedb.all(`SELECT *, datetime(date, 'unixepoch') AS XFORMATTEDDATESTRING from voicemail ORDER BY date ASC`, async function (err, rows) {
  465. if (err) reject(err)
  466. resolve(rows)
  467. })
  468. })
  469. }
  470. getVoicemailFileList () {
  471. return new Promise((resolve, reject) => {
  472. this.openDatabase('Manifest.db', true)
  473. .then(manifestdb => {
  474. manifestdb.all(`SELECT * from FILES where relativePath like 'Library/Voicemail/%.amr'`, async (err, rows) => {
  475. if (err) reject(err)
  476. resolve(rows)
  477. })
  478. })
  479. .catch(reject)
  480. })
  481. }
  482. getWifiList () {
  483. return new Promise((resolve, reject) => {
  484. var filename = this.getFileName(databases.WiFi)
  485. try {
  486. let wifiList = plist.parseFile(filename)
  487. wifiList['List of known networks'] = wifiList['List of known networks']
  488. .map(el => {
  489. if (el.BSSID) {
  490. el.BSSID = macParse.pad_zeros(el.BSSID) + ''
  491. }
  492. return el
  493. })
  494. resolve(wifiList)
  495. } catch (e) {
  496. reject(e)
  497. }
  498. })
  499. }
  500. getCookies () {
  501. return new Promise(async (resolve, reject) => {
  502. this.openDatabase('Manifest.db', true)
  503. .then(manifestdb => {
  504. manifestdb.all(`SELECT fileID,domain,relativePath from FILES where relativePath like 'Library/Cookies/Cookies.binarycookies'`, async (err, rows) => {
  505. if (err) return reject(err)
  506. let cookiesResult = []
  507. const iterateElements = (elements, index, callback) => {
  508. if (index === elements.length) { return callback() }
  509. // do parse call with element
  510. var ele = elements[index]
  511. cookieParser.parse(this.getFileName(ele.fileID))
  512. .then(cookies => {
  513. // Map to include domain
  514. let formatted = cookies.map(el => { return { domain: ele.domain, cookie: el } })
  515. // Append result
  516. cookiesResult = [...cookiesResult, ...formatted]
  517. // Next file.
  518. iterateElements(elements, index + 1, callback)
  519. })
  520. }
  521. iterateElements(rows, 0, () => {
  522. resolve(cookiesResult)
  523. })
  524. })
  525. })
  526. .catch(reject)
  527. })
  528. }
  529. getAddressBook () {
  530. return new Promise((resolve, reject) => {
  531. let addressbookdb = this.getDatabase(databases.AddressBook)
  532. const self = this
  533. try {
  534. // Query basic Address Book fields
  535. const query = `
  536. select ABPerson.ROWID
  537. , ABPerson.first
  538. , ABPerson.middle
  539. , ABPerson.last
  540. , ABPerson.Organization as organization
  541. , ABPerson.Department as department
  542. , ABPerson.Birthday as birthday
  543. , ABPerson.JobTitle as jobtitle
  544. , datetime(ABPerson.CreationDate + 978307200, 'unixepoch') as created_date
  545. , datetime(ABPerson.ModificationDate + 978307200, 'unixepoch') as updated_date
  546. , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Work>!$_')) as phone_work
  547. , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Mobile>!$_')) as phone_mobile
  548. , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Home>!$_')) as phone_home
  549. , (select value from ABMultiValue where property = 4 and record_id = ABPerson.ROWID) as email
  550. , (select value from ABMultiValueEntry where parent_id in (select ROWID from ABMultiValue where record_id = ABPerson.ROWID) and key = (select ROWID from ABMultiValueEntryKey where lower(value) = 'street')) as address
  551. , (select value from ABMultiValueEntry where parent_id in (select ROWID from ABMultiValue where record_id = ABPerson.ROWID) and key = (select ROWID from ABMultiValueEntryKey where lower(value) = 'city')) as city
  552. , ABPerson.Note as note
  553. from ABPerson
  554. order by ABPerson.ROWID
  555. `
  556. addressbookdb.all(query, async function (err, rows) {
  557. if (err) reject(err)
  558. const iterateElements = (elements, index, callback) => {
  559. if (index === elements.length) { return callback() }
  560. // do parse call with element
  561. let ele = elements[index]
  562. //Query username and profile links for other services (facebook etc)
  563. const query = `
  564. select (select value from ABMultiValue where property = 22 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = 'PROFILE')) as google_profile
  565. , (select value from ABMultiValue where property = 22 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = 'profile')) as google_profile1
  566. , (select value from ABMultiValue where property = 4 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = 'iCloud')) as icloud
  567. , (select value from ABMultiValueEntry where parent_id in (select ROWID from ABMultiValue where record_id = ABPerson.ROWID) and key = (select ROWID from ABMultiValueEntryKey where lower(value) = 'service')) as service
  568. , (select value from ABMultiValueEntry where parent_id in (select ROWID from ABMultiValue where record_id = ABPerson.ROWID) and key = (select ROWID from ABMultiValueEntryKey where lower(value) = 'username')) as username
  569. , (select value from ABMultiValueEntry where parent_id in (select ROWID from ABMultiValue where record_id = ABPerson.ROWID) and key = (select ROWID from ABMultiValueEntryKey where lower(value) = 'url')) as url
  570. from ABPerson
  571. where ABPerson.ROWID = ${ele.ROWID}
  572. order by ABPerson.ROWID
  573. `
  574. addressbookdb.all(query, async function (err, rows1) {
  575. if (err) reject(err)
  576. rows1[0].google_profile = rows1[0].google_profile || rows1[0].google_profile1
  577. delete rows1[0].google_profile1
  578. ele.services = rows1[0]
  579. const addressbookimagesdb = self.getDatabase(databases.AddressBookImages)
  580. //Query profile picture extraction from /Library/AddressBook/AddressBookImages.sqlitedb
  581. const query = `
  582. select data
  583. from ABFullSizeImage
  584. where ABFullSizeImage.record_id = ${ele.ROWID}
  585. `
  586. addressbookimagesdb.get(query, async function (err, row) {
  587. if (err) reject(err)
  588. ele.profile_picture = null
  589. if (row) {
  590. ele.profile_picture = (row.data || '').toString('base64')
  591. }
  592. iterateElements(elements, index + 1, callback)
  593. })
  594. })
  595. }
  596. iterateElements(rows, 0, () => {
  597. resolve(rows)
  598. })
  599. })
  600. } catch (e) {
  601. reject(e)
  602. }
  603. })
  604. }
  605. getSafariBookmarks () {
  606. return new Promise((resolve, reject) => {
  607. var bookmarksdb = this.getDatabase(databases.SafariBookmarks)
  608. try {
  609. const query = `
  610. select bookmarks.id
  611. , bookmarks.title
  612. , bookmarks.url
  613. , bookmarks.parent as parent_id
  614. , bookmarks.special_id
  615. , bookmarks.type
  616. , bookmarks.num_children
  617. , bookmarks.editable
  618. , bookmarks.deletable
  619. , bookmarks.hidden
  620. , bookmarks.hidden_ancestor_count
  621. , bookmarks.order_index
  622. , bookmarks.external_uuid
  623. , bookmarks.read
  624. , bookmarks.last_modified
  625. , bookmarks.server_id
  626. , bookmarks.sync_key
  627. , bookmarks.added
  628. , bookmarks.deleted
  629. , bookmarks.fetched_icon
  630. , bookmarks.dav_generation
  631. , bookmarks.locally_added
  632. , bookmarks.archive_status
  633. , bookmarks.syncable
  634. , bookmarks.web_filter_status
  635. , bookmarks.modified_attributes
  636. , parent_bookmarks.title as parent_title
  637. from bookmarks
  638. left join bookmarks as parent_bookmarks on parent_bookmarks.id = bookmarks.parent
  639. where bookmarks.type = 0
  640. order by bookmarks.id
  641. `
  642. bookmarksdb.all(query, async function (err, rows) {
  643. if (err) reject(err)
  644. resolve(rows)
  645. })
  646. } catch (e) {
  647. reject(e)
  648. }
  649. })
  650. }
  651. getSafariBookmarksiOS7 () {
  652. return new Promise((resolve, reject) => {
  653. var bookmarksdb = this.getDatabase(databases.SafariBookmarks)
  654. try {
  655. const query = `
  656. select bookmarks.id
  657. , bookmarks.special_id
  658. , bookmarks.parent as parent_id
  659. , bookmarks.type
  660. , bookmarks.title
  661. , bookmarks.url
  662. , bookmarks.num_children
  663. , bookmarks.editable
  664. , bookmarks.deletable
  665. , bookmarks.hidden
  666. , bookmarks.hidden_ancestor_count
  667. , bookmarks.order_index
  668. , bookmarks.external_uuid
  669. , bookmarks.read
  670. , bookmarks.last_modified
  671. , bookmarks.server_id
  672. , bookmarks.sync_key
  673. , bookmarks.sync_data
  674. , bookmarks.added
  675. , bookmarks.deleted
  676. , bookmarks.extra_attributes
  677. , bookmarks.local_attributes
  678. , bookmarks.fetched_icon
  679. , bookmarks.icon
  680. , bookmarks.dav_generation
  681. , bookmarks.locally_added
  682. , bookmarks.archive_status
  683. , bookmarks.syncable
  684. , bookmarks.web_filter_status
  685. , parent_bookmarks.title as parent_title
  686. from bookmarks
  687. left join bookmarks as parent_bookmarks on parent_bookmarks.id = bookmarks.parent
  688. where bookmarks.type = 0
  689. order by bookmarks.id
  690. `
  691. bookmarksdb.all(query, async function (err, rows) {
  692. if (err) reject(err)
  693. resolve(rows)
  694. })
  695. } catch (e) {
  696. reject(e)
  697. }
  698. })
  699. }
  700. getPushstore () {
  701. return new Promise((resolve, reject) => {
  702. this.getFileManifest().then((manifest) => {
  703. try {
  704. let files = manifest.filter((file) => {
  705. if (file.relativePath)
  706. return ~file.relativePath.indexOf("Library/SpringBoard/PushStore/")
  707. return false
  708. })
  709. const pushstores = []
  710. files.forEach((file) => {
  711. let data = plist.parseFile(this.getFileName(file.fileID))
  712. pushstores.push(...pushstoreParse.run(data))
  713. })
  714. resolve(pushstores)
  715. } catch (e) {
  716. reject(e)
  717. }
  718. })
  719. })
  720. }
  721. getOldPushstore () {
  722. return new Promise((resolve, reject) => {
  723. this.getOldFileManifest().then((manifest) => {
  724. try {
  725. let files = manifest.filter((file) => {
  726. if (file.filename) {
  727. return ~file.filename.indexOf("Library/SpringBoard/PushStore/")
  728. }
  729. return false
  730. })
  731. let pushstores = []
  732. files.forEach((file) => {
  733. let data = plist.parseFile(this.getFileName(file.fileID))
  734. pushstores.push(...pushstoreParse.run(data))
  735. })
  736. resolve(pushstores)
  737. } catch (e) {
  738. reject(e)
  739. }
  740. })
  741. })
  742. }
  743. }
  744. module.exports.availableBackups = function () {
  745. const base = path.join(os.homedir(), '/Library/Application Support/MobileSync/Backup/')
  746. return new Promise((resolve, reject) => {
  747. resolve(fs.readdirSync(base, {
  748. encoding: 'utf8'
  749. })
  750. .map(file => IPhoneBackup.fromID(file)))
  751. })
  752. }
  753. module.exports.iPhoneBackup = IPhoneBackup
  754. module.exports.IPhoneBackup = IPhoneBackup