iphone_backup.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862
  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. getMessageAttachments (messageId) {
  248. var backup = this
  249. return new Promise((resolve, reject) => {
  250. const messagedb = this.getDatabase(databases.SMS)
  251. const query = `
  252. SELECT
  253. attachment.*
  254. FROM message_attachment_join
  255. INNER JOIN attachment
  256. ON attachment.ROWID = message_attachment_join.attachment_id
  257. WHERE message_attachment_join.message_id = ${parseInt(messageId)}
  258. `
  259. messagedb.all(query,
  260. async function (err, attachments) {
  261. if (err) return reject(err)
  262. attachments = attachments || []
  263. resolve(attachments)
  264. })
  265. })
  266. }
  267. getConversationsiOS9 () {
  268. var backup = this
  269. return new Promise((resolve, reject) => {
  270. var messagedb = this.getDatabase(databases.SMS)
  271. messagedb.all(`SELECT * FROM chat ORDER BY ROWID ASC`, async function (err, rows) {
  272. if (err) return reject(err)
  273. rows = rows || []
  274. // We need to do some manual parsing of these records.
  275. // The timestamp information is stored in a binary blob named `properties`
  276. // Which is formatted as a binary PLIST.
  277. for (var el of rows) {
  278. if (el.properties) el.properties = bplist.parseBuffer(el.properties)[0]
  279. // Interestingly, some of these do not have dates attached.
  280. if (el.properties) {
  281. el.date = new Date(el.properties.CKChatWatermarkTime * 1000)
  282. } else {
  283. el.date = new Date(0)
  284. }
  285. var contact = await backup.getName(el.chat_identifier)
  286. if (contact) {
  287. el.display_name = `${contact.name} <${contact.query}>`
  288. }
  289. // Format as YY-MM-DD HH:MM:SS
  290. try {
  291. el.XFORMATTEDDATESTRING = el.date.toISOString()
  292. .split('T')
  293. .join(' ')
  294. .split('Z')
  295. .join(' ')
  296. .split('.')[0]
  297. .trim()
  298. } catch (e) {
  299. el.XFORMATTEDDATESTRING = ''
  300. }
  301. }
  302. // Sort by the date.
  303. rows = rows.sort(function (a, b) {
  304. return (a.date.getTime() || 0) - (b.date.getTime() || 0)
  305. })
  306. resolve(rows)
  307. })
  308. })
  309. }
  310. getConversationsiOS10iOS11 () {
  311. return new Promise((resolve, reject) => {
  312. var messagedb = this.getDatabase(databases.SMS)
  313. 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) {
  314. if (err) return reject(err)
  315. rows = rows || []
  316. resolve(rows)
  317. })
  318. })
  319. }
  320. getConversations () {
  321. if (parseInt(this.manifest.Lockdown.BuildVersion) <= 14) {
  322. return this.getConversationsiOS9()
  323. } else {
  324. return this.getConversationsiOS10iOS11()
  325. }
  326. }
  327. getFileManifest () {
  328. return new Promise((resolve, reject) => {
  329. var messagedb = this.getDatabase('Manifest.db', true)
  330. messagedb.all('SELECT * from FILES', async function (err, rows) {
  331. if (err) reject(err)
  332. resolve(rows)
  333. })
  334. })
  335. }
  336. getOldFileManifest () {
  337. return new Promise((resolve, reject) => {
  338. let mbdbPath = this.getFileName('Manifest.mbdb', true)
  339. manifestMBDBParse.process(mbdbPath, resolve, reject)
  340. })
  341. }
  342. getOldNotes () {
  343. return new Promise((resolve, reject) => {
  344. var messagedb = this.getDatabase(databases.Notes)
  345. messagedb.all(`SELECT *, datetime(ZCREATIONDATE + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING from ZNOTE LEFT JOIN ZNOTEBODY ON ZBODY = ZNOTEBODY.Z_PK`, async function (err, rows) {
  346. if (err) reject(err)
  347. resolve(rows)
  348. })
  349. })
  350. }
  351. getNewNotesiOS9 () {
  352. return new Promise((resolve, reject) => {
  353. var messagedb = this.getDatabase(databases.Notes2)
  354. messagedb.all(`SELECT *, datetime(ZCREATIONDATE + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING FROM ZICCLOUDSYNCINGOBJECT`, async function (err, rows) {
  355. if (err) reject(err)
  356. resolve(rows)
  357. })
  358. })
  359. }
  360. getNewNotesiOS10iOS11 () {
  361. return new Promise((resolve, reject) => {
  362. var messagedb = this.getDatabase(databases.Notes2)
  363. messagedb.all(`SELECT *, datetime(ZCREATIONDATE + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING, datetime(ZCREATIONDATE1 + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING1 FROM ZICCLOUDSYNCINGOBJECT`, async function (err, rows) {
  364. if (err) reject(err)
  365. resolve(rows)
  366. })
  367. })
  368. }
  369. getNotes () {
  370. if (parseInt(this.manifest.Lockdown.BuildVersion) <= 13) {
  371. // Legacy iOS 9 support
  372. // May work for earlier but I haven't tested it
  373. return this.getNewNotesiOS9()
  374. } else {
  375. return this.getNewNotesiOS10iOS11()
  376. }
  377. }
  378. getWebHistory () {
  379. return new Promise((resolve, reject) => {
  380. var messagedb = this.getDatabase(databases.WebHistory)
  381. 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) {
  382. if (err) reject(err)
  383. resolve(rows)
  384. })
  385. })
  386. }
  387. getPhotoLocationHistory () {
  388. return new Promise((resolve, reject) => {
  389. var messagedb = this.getDatabase(databases.Photos)
  390. messagedb.all(`SELECT ZDATECREATED, ZLATITUDE, ZLONGITUDE, ZFILENAME, datetime(ZDATECREATED + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING FROM ZGENERICASSET ORDER BY ZDATECREATED ASC`, async function (err, rows) {
  391. if (err) reject(err)
  392. resolve(rows)
  393. })
  394. })
  395. }
  396. getGeofencesList () {
  397. return new Promise((resolve, reject) => {
  398. var messagedb = this.getDatabase(databases.Locations)
  399. messagedb.all(`SELECT datetime(Timestamp + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING, Latitude, Longitude, Distance FROM Fences ORDER BY Timestamp ASC`, async function (err, rows) {
  400. if (err) reject(err)
  401. resolve(rows)
  402. })
  403. })
  404. }
  405. getCallsStatisticsiOS7 () {
  406. return new Promise((resolve, reject) => {
  407. var messagedb = this.getDatabase(databases.Calls)
  408. messagedb.all(`SELECT * from _SqliteDatabaseProperties`, async function (err, rows) {
  409. if (err) reject(err)
  410. resolve(rows)
  411. })
  412. })
  413. }
  414. getCallsStatistics () {
  415. return new Promise((resolve, reject) => {
  416. var messagedb = this.getDatabase(databases.Calls2)
  417. messagedb.all(`SELECT * from ZCALLDBPROPERTIES`, async function (err, rows) {
  418. if (err) reject(err)
  419. resolve(rows)
  420. })
  421. })
  422. }
  423. getCallsList () {
  424. if (parseInt(this.manifest.Lockdown.BuildVersion) <= 13) {
  425. // Legacy iOS 9 support
  426. // May work for earlier but I haven't tested it
  427. return this.getCallsListiOS7()
  428. } else {
  429. return this.getCallsListLater()
  430. }
  431. }
  432. getCallsListiOS7 () {
  433. return new Promise((resolve, reject) => {
  434. var messagedb = this.getDatabase(databases.Calls)
  435. messagedb.all(`SELECT
  436. ROWID as Z_PK,
  437. datetime(date, 'unixepoch') AS XFORMATTEDDATESTRING,
  438. answered as ZANSWERED,
  439. duration as ZDURATION,
  440. address as ZADDRESS,
  441. country_code as ZISO_COUNTRY_CODE,
  442. country_code as ZISO_COUNTRY_CODE,
  443. * from call ORDER BY date ASC`, async function (err, rows) {
  444. if (err) reject(err)
  445. resolve(rows)
  446. })
  447. })
  448. }
  449. getCallsListLater () {
  450. return new Promise((resolve, reject) => {
  451. var messagedb = this.getDatabase(databases.Calls2)
  452. messagedb.all(`SELECT *, datetime(ZDATE + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING from ZCALLRECORD ORDER BY ZDATE ASC`, async function (err, rows) {
  453. if (err) reject(err)
  454. resolve(rows)
  455. })
  456. })
  457. }
  458. getVoicemailsList () {
  459. return new Promise((resolve, reject) => {
  460. var messagedb = this.getDatabase(databases.Voicemail)
  461. messagedb.all(`SELECT *, datetime(date, 'unixepoch') AS XFORMATTEDDATESTRING from voicemail ORDER BY date ASC`, async function (err, rows) {
  462. if (err) reject(err)
  463. resolve(rows)
  464. })
  465. })
  466. }
  467. getVoicemailFileList () {
  468. return new Promise((resolve, reject) => {
  469. this.openDatabase('Manifest.db', true)
  470. .then(manifestdb => {
  471. manifestdb.all(`SELECT * from FILES where relativePath like 'Library/Voicemail/%.amr'`, async (err, rows) => {
  472. if (err) reject(err)
  473. resolve(rows)
  474. })
  475. })
  476. .catch(reject)
  477. })
  478. }
  479. getWifiList () {
  480. return new Promise((resolve, reject) => {
  481. var filename = this.getFileName(databases.WiFi)
  482. try {
  483. let wifiList = bplist.parseBuffer(fs.readFileSync(filename))[0]
  484. wifiList['List of known networks'] = wifiList['List of known networks']
  485. .map(el => {
  486. if (el.BSSID) {
  487. el.BSSID = macParse.pad_zeros(el.BSSID) + ''
  488. }
  489. return el
  490. })
  491. resolve(wifiList)
  492. } catch (e) {
  493. reject(e)
  494. }
  495. })
  496. }
  497. getCookies () {
  498. return new Promise(async (resolve, reject) => {
  499. this.openDatabase('Manifest.db', true)
  500. .then(manifestdb => {
  501. manifestdb.all(`SELECT fileID,domain,relativePath from FILES where relativePath like 'Library/Cookies/Cookies.binarycookies'`, async (err, rows) => {
  502. if (err) return reject(err)
  503. let cookiesResult = []
  504. const iterateElements = (elements, index, callback) => {
  505. if (index === elements.length) { return callback() }
  506. // do parse call with element
  507. var ele = elements[index]
  508. cookieParser.parse(this.getFileName(ele.fileID))
  509. .then(cookies => {
  510. // Map to include domain
  511. let formatted = cookies.map(el => { return { domain: ele.domain, cookie: el } })
  512. // Append result
  513. cookiesResult = [...cookiesResult, ...formatted]
  514. // Next file.
  515. iterateElements(elements, index + 1, callback)
  516. })
  517. }
  518. iterateElements(rows, 0, () => {
  519. resolve(cookiesResult)
  520. })
  521. })
  522. })
  523. .catch(reject)
  524. })
  525. }
  526. getAddressBook () {
  527. return new Promise((resolve, reject) => {
  528. let addressbookdb = this.getDatabase(databases.AddressBook)
  529. const self = this
  530. try {
  531. // Query basic Address Book fields
  532. const query = `
  533. select ABPerson.ROWID
  534. , ABPerson.first
  535. , ABPerson.middle
  536. , ABPerson.last
  537. , ABPerson.Organization as organization
  538. , ABPerson.Department as department
  539. , ABPerson.Birthday as birthday
  540. , ABPerson.JobTitle as jobtitle
  541. , datetime(ABPerson.CreationDate + 978307200, 'unixepoch') as created_date
  542. , datetime(ABPerson.ModificationDate + 978307200, 'unixepoch') as updated_date
  543. , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Work>!$_')) as phone_work
  544. , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Mobile>!$_')) as phone_mobile
  545. , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Home>!$_')) as phone_home
  546. , (select value from ABMultiValue where property = 4 and record_id = ABPerson.ROWID) as email
  547. , (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
  548. , (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
  549. , ABPerson.Note as note
  550. from ABPerson
  551. order by ABPerson.ROWID
  552. `
  553. addressbookdb.all(query, async function (err, rows) {
  554. if (err) reject(err)
  555. const iterateElements = (elements, index, callback) => {
  556. if (index === elements.length) { return callback() }
  557. // do parse call with element
  558. let ele = elements[index]
  559. //Query username and profile links for other services (facebook etc)
  560. const query = `
  561. 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
  562. , (select value from ABMultiValue where property = 22 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = 'profile')) as google_profile1
  563. , (select value from ABMultiValue where property = 4 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = 'iCloud')) as icloud
  564. , (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
  565. , (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
  566. , (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
  567. from ABPerson
  568. where ABPerson.ROWID = ${ele.ROWID}
  569. order by ABPerson.ROWID
  570. `
  571. addressbookdb.all(query, async function (err, rows1) {
  572. if (err) reject(err)
  573. rows1[0].google_profile = rows1[0].google_profile || rows1[0].google_profile1
  574. delete rows1[0].google_profile1
  575. ele.services = rows1[0]
  576. const addressbookimagesdb = self.getDatabase(databases.AddressBookImages)
  577. //Query profile picture extraction from /Library/AddressBook/AddressBookImages.sqlitedb
  578. const query = `
  579. select data
  580. from ABFullSizeImage
  581. where ABFullSizeImage.record_id = ${ele.ROWID}
  582. `
  583. addressbookimagesdb.get(query, async function (err, row) {
  584. if (err) reject(err)
  585. ele.profile_picture = null
  586. if (row) {
  587. ele.profile_picture = row.data.toString('base64')
  588. }
  589. iterateElements(elements, index + 1, callback)
  590. })
  591. })
  592. }
  593. iterateElements(rows, 0, () => {
  594. resolve(rows)
  595. })
  596. })
  597. } catch (e) {
  598. reject(e)
  599. }
  600. })
  601. }
  602. getSafariBookmarks () {
  603. return new Promise((resolve, reject) => {
  604. var bookmarksdb = this.getDatabase(databases.SafariBookmarks)
  605. try {
  606. const query = `
  607. select bookmarks.id
  608. , bookmarks.title
  609. , bookmarks.url
  610. , bookmarks.parent as parent_id
  611. , bookmarks.special_id
  612. , bookmarks.type
  613. , bookmarks.num_children
  614. , bookmarks.editable
  615. , bookmarks.deletable
  616. , bookmarks.hidden
  617. , bookmarks.hidden_ancestor_count
  618. , bookmarks.order_index
  619. , bookmarks.external_uuid
  620. , bookmarks.read
  621. , bookmarks.last_modified
  622. , bookmarks.server_id
  623. , bookmarks.sync_key
  624. , bookmarks.added
  625. , bookmarks.deleted
  626. , bookmarks.fetched_icon
  627. , bookmarks.dav_generation
  628. , bookmarks.locally_added
  629. , bookmarks.archive_status
  630. , bookmarks.syncable
  631. , bookmarks.web_filter_status
  632. , bookmarks.modified_attributes
  633. , parent_bookmarks.title as parent_title
  634. from bookmarks
  635. left join bookmarks as parent_bookmarks on parent_bookmarks.id = bookmarks.parent
  636. where bookmarks.type = 0
  637. order by bookmarks.id
  638. `
  639. bookmarksdb.all(query, async function (err, rows) {
  640. if (err) reject(err)
  641. resolve(rows)
  642. })
  643. } catch (e) {
  644. reject(e)
  645. }
  646. })
  647. }
  648. getSafariBookmarksiOS7 () {
  649. return new Promise((resolve, reject) => {
  650. var bookmarksdb = this.getDatabase(databases.SafariBookmarks)
  651. try {
  652. const query = `
  653. select bookmarks.id
  654. , bookmarks.special_id
  655. , bookmarks.parent as parent_id
  656. , bookmarks.type
  657. , bookmarks.title
  658. , bookmarks.url
  659. , bookmarks.num_children
  660. , bookmarks.editable
  661. , bookmarks.deletable
  662. , bookmarks.hidden
  663. , bookmarks.hidden_ancestor_count
  664. , bookmarks.order_index
  665. , bookmarks.external_uuid
  666. , bookmarks.read
  667. , bookmarks.last_modified
  668. , bookmarks.server_id
  669. , bookmarks.sync_key
  670. , bookmarks.sync_data
  671. , bookmarks.added
  672. , bookmarks.deleted
  673. , bookmarks.extra_attributes
  674. , bookmarks.local_attributes
  675. , bookmarks.fetched_icon
  676. , bookmarks.icon
  677. , bookmarks.dav_generation
  678. , bookmarks.locally_added
  679. , bookmarks.archive_status
  680. , bookmarks.syncable
  681. , bookmarks.web_filter_status
  682. , parent_bookmarks.title as parent_title
  683. from bookmarks
  684. left join bookmarks as parent_bookmarks on parent_bookmarks.id = bookmarks.parent
  685. where bookmarks.type = 0
  686. order by bookmarks.id
  687. `
  688. bookmarksdb.all(query, async function (err, rows) {
  689. if (err) reject(err)
  690. resolve(rows)
  691. })
  692. } catch (e) {
  693. reject(e)
  694. }
  695. })
  696. }
  697. getPushstore () {
  698. return new Promise((resolve, reject) => {
  699. this.getFileManifest().then((manifest) => {
  700. try {
  701. let files = manifest.filter((file) => {
  702. if (file.relativePath)
  703. return ~file.relativePath.indexOf("Library/SpringBoard/PushStore/")
  704. return false
  705. })
  706. const pushstores = []
  707. files.forEach((file) => {
  708. let plist = bplist.parseBuffer(fs.readFileSync(this.getFileName(file.fileID)))[0]
  709. pushstores.push(...pushstoreParse.run(plist))
  710. })
  711. resolve(pushstores)
  712. } catch (e) {
  713. reject(e)
  714. }
  715. })
  716. })
  717. }
  718. getOldPushstore () {
  719. return new Promise((resolve, reject) => {
  720. this.getOldFileManifest().then((manifest) => {
  721. try {
  722. let files = manifest.filter((file) => {
  723. if (file.filename) {
  724. return ~file.filename.indexOf("Library/SpringBoard/PushStore/")
  725. }
  726. return false
  727. })
  728. let pushstores = []
  729. files.forEach((file) => {
  730. let plist = bplist.parseBuffer(fs.readFileSync(this.getFileName(file.fileID)))[0]
  731. pushstores.push(...pushstoreParse.run(plist))
  732. })
  733. resolve(pushstores)
  734. } catch (e) {
  735. reject(e)
  736. }
  737. })
  738. })
  739. }
  740. }
  741. module.exports.availableBackups = function () {
  742. const base = path.join(process.env.HOME, '/Library/Application Support/MobileSync/Backup/')
  743. return new Promise((resolve, reject) => {
  744. resolve(fs.readdirSync(base, {
  745. encoding: 'utf8'
  746. })
  747. .map(file => IPhoneBackup.fromID(file)))
  748. })
  749. }
  750. module.exports.iPhoneBackup = IPhoneBackup
  751. module.exports.IPhoneBackup = IPhoneBackup