iphone_backup.js 28 KB

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