|
@@ -4,12 +4,15 @@ const bplist = require('bplist-parser')
|
|
|
const fs = require('fs')
|
|
|
const plist = require('plist')
|
|
|
const mac_address_parse = require('./mac_address_parse')
|
|
|
+const cookieParser = require('binary-cookies')()
|
|
|
const tz_offset = 5
|
|
|
|
|
|
const databases = {
|
|
|
SMS: '3d0d7e5fb2ce288813306e4d4636395e047a3d28',
|
|
|
+ AddressBook: '31bb7ba8914766d4ba40d6dfb6113c8b614be442',
|
|
|
Contacts: '31bb7ba8914766d4ba40d6dfb6113c8b614be442',
|
|
|
Calendar: '2041457d5fe04d39d0ab481178355df6781e6858',
|
|
|
+ 'Cookies.binarycookies': '69b1865768101bacde5b77ccc44445cea9ce1261',
|
|
|
Reminders: '2041457d5fe04d39d0ab481178355df6781e6858',
|
|
|
Notes: 'ca3bc056d4da0bbf88b5fb3be254f3b7147e639c',
|
|
|
Notes2: '4f98687d8ab0d6d1a371110e6b7300f6e465bef2',
|
|
@@ -391,6 +394,57 @@ class iPhoneBackup {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ getCallsStatisticsiOS7 () {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ var messagedb = this.getDatabase(databases.Calls)
|
|
|
+ messagedb.all(`SELECT * from _SqliteDatabaseProperties`, async function (err, rows) {
|
|
|
+ if (err) reject(err)
|
|
|
+ resolve(rows)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ getCallsStatistics () {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ var messagedb = this.getDatabase(databases.Calls2)
|
|
|
+ messagedb.all(`SELECT * from ZCALLDBPROPERTIES`, async function (err, rows) {
|
|
|
+ if (err) reject(err)
|
|
|
+ resolve(rows)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ getCallsList () {
|
|
|
+ if (parseInt(this.manifest.Lockdown.BuildVersion) <= 13) {
|
|
|
+ // Legacy iOS 9 support
|
|
|
+ // May work for earlier but I haven't tested it
|
|
|
+ return this.getCallsListiOS7()
|
|
|
+ } else {
|
|
|
+ return this.getCallsList()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ getCallsListiOS7 () {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ var messagedb = this.getDatabase(databases.Calls)
|
|
|
+ messagedb.all(`SELECT
|
|
|
+ ROWID as Z_PK,
|
|
|
+ datetime(date, 'unixepoch') AS XFORMATTEDDATESTRING,
|
|
|
+ answered as ZANSWERED,
|
|
|
+ duration as ZDURATION,
|
|
|
+ address as ZADDRESS,
|
|
|
+ country_code as ZISO_COUNTRY_CODE,
|
|
|
+ country_code as ZISO_COUNTRY_CODE,
|
|
|
+ * from call ORDER BY date ASC`, async function (err, rows) {
|
|
|
+ if (err) reject(err)
|
|
|
+
|
|
|
+ resolve(rows)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
getCallsList () {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
var messagedb = this.getDatabase(databases.Calls2)
|
|
@@ -441,6 +495,84 @@ class iPhoneBackup {
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
+
|
|
|
+ getCookies () {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const self = this
|
|
|
+ var manifestdb = this.getDatabase('Manifest.db', true)
|
|
|
+ manifestdb.all(`SELECT fileID,domain,relativePath from FILES where relativePath like 'Library/Cookies/Cookies.binarycookies'`, async function (err, rows) {
|
|
|
+ if (err) reject(err)
|
|
|
+ let cookiesResult = [];
|
|
|
+ const iterateElements = (elements, index, callback) => {
|
|
|
+ if (index == elements.length)
|
|
|
+ return callback();
|
|
|
+ // do parse call with element
|
|
|
+ var ele = elements[index];
|
|
|
+ cookieParser.parse(self.getFileName(ele.fileID), function(err, cookies) {
|
|
|
+ //console.log(ele.domain, ':', cookies)
|
|
|
+
|
|
|
+ if (err) {
|
|
|
+ cookiesResult.push({
|
|
|
+ domain: ele.domain,
|
|
|
+ error: err
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ cookies.forEach(cookie => {
|
|
|
+ cookie.url = cookie.url.replace(/\0/g, '')
|
|
|
+ cookie.name = cookie.name.replace(/\0/g, '')
|
|
|
+ cookie.path = cookie.path.replace(/\0/g, '')
|
|
|
+ cookie.value = cookie.value.replace(/\0/g, '')
|
|
|
+ cookiesResult.push({
|
|
|
+ domain: ele.domain,
|
|
|
+ cookie: cookie
|
|
|
+ })
|
|
|
+ });
|
|
|
+ }
|
|
|
+ iterateElements(elements, index+1, callback);
|
|
|
+ })
|
|
|
+ }
|
|
|
+ iterateElements(rows, 0, () => {
|
|
|
+ resolve(cookiesResult)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ getAddressBook () {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ var addressbookdb = this.getDatabase(databases.AddressBook);
|
|
|
+ try {
|
|
|
+ const query = `
|
|
|
+ select ABPerson.ROWID
|
|
|
+ , ABPerson.first
|
|
|
+ , ABPerson.middle
|
|
|
+ , ABPerson.last
|
|
|
+ , ABPerson.Organization as organization
|
|
|
+ , ABPerson.Department as department
|
|
|
+ , ABPerson.Birthday as birthday
|
|
|
+ , ABPerson.JobTitle as jobtitle
|
|
|
+
|
|
|
+ , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Work>!$_')) as phone_work
|
|
|
+ , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Mobile>!$_')) as phone_mobile
|
|
|
+ , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Home>!$_')) as phone_home
|
|
|
+
|
|
|
+ , (select value from ABMultiValue where property = 4 and record_id = ABPerson.ROWID) as email
|
|
|
+
|
|
|
+ , (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
|
|
|
+ , (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
|
|
|
+ from ABPerson
|
|
|
+ order by ABPerson.ROWID
|
|
|
+ `
|
|
|
+ addressbookdb.all(query, async function (err, rows) {
|
|
|
+ if (err) reject(err)
|
|
|
+
|
|
|
+ resolve(rows)
|
|
|
+ })
|
|
|
+ } catch (e) {
|
|
|
+ reject(e)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
module.exports.availableBackups = function () {
|