|
@@ -0,0 +1,125 @@
|
|
|
+
|
|
|
+const ARCHIVER_KEY = '$archiver'
|
|
|
+const TOP_KEY = '$top'
|
|
|
+const OBJECTS_KEY = '$objects'
|
|
|
+const CLASS_KEY = '$class'
|
|
|
+const CLASSNAME_KEY = '$classname'
|
|
|
+const NS_OBJECTS = 'NS.objects'
|
|
|
+const NS_KEYS = 'NS.keys'
|
|
|
+const NS_TIME = 'NS.time'
|
|
|
+const NS_MUTABLE_DICTIONARY = 'NSMutableDictionary'
|
|
|
+const DEFAULT_KEYS = ['AppNotificationCreationDate',
|
|
|
+ 'RequestedDate',
|
|
|
+ 'TriggerDate',
|
|
|
+ 'AppNotificationMessage']
|
|
|
+const UNIX_OFFSET = 978307200
|
|
|
+
|
|
|
+const run = (pushstore) => {
|
|
|
+ const pushstoreEntries = []
|
|
|
+ if (is_valid_pushstore(pushstore)) {
|
|
|
+ let top = get_top(pushstore)
|
|
|
+ if (top == -1) {
|
|
|
+ throw "Unable to get $top location!"
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ pushstore.objects_list = pushstore[OBJECTS_KEY]
|
|
|
+
|
|
|
+ let pointer_to_entries = load_from_location(pushstore, top)
|
|
|
+ pointer_to_entries['objects'].every((entry_offset) => {
|
|
|
+ let entry_dict = make_entry_dict(pushstore, load_from_location(pushstore, entry_offset))
|
|
|
+ let formatted = format_entry(pushstore, entry_dict)
|
|
|
+ pushstoreEntries.push(formatted)
|
|
|
+ return true
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return pushstoreEntries
|
|
|
+}
|
|
|
+
|
|
|
+const is_valid_pushstore = (pushstore) => {
|
|
|
+ // Check version and archiver key
|
|
|
+ return pushstore[ARCHIVER_KEY] === "NSKeyedArchiver"
|
|
|
+}
|
|
|
+
|
|
|
+const get_top = (pushstore) => {
|
|
|
+ // Find pointer in $objects to starting point
|
|
|
+ try {
|
|
|
+ return parseInt(pushstore[TOP_KEY]['root']['UID'])
|
|
|
+ } catch (e) {
|
|
|
+ throw e
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const load_from_location = (pushstore, offset) => {
|
|
|
+ // Load objects (and keys) from a location
|
|
|
+ let loaded_dict = {}
|
|
|
+ let start = pushstore.objects_list[offset]
|
|
|
+ //pushstore.start = start
|
|
|
+ let loaded_class = get_classname_at(start, pushstore)
|
|
|
+
|
|
|
+ if (!loaded_class) {
|
|
|
+ throw "Unable to determine $classname of key!"
|
|
|
+ }
|
|
|
+ loaded_dict['class'] = loaded_class
|
|
|
+ loaded_dict['objects'] = start[NS_OBJECTS].map((x) => parseInt(x['UID']))
|
|
|
+
|
|
|
+ if (loaded_class === NS_MUTABLE_DICTIONARY) {
|
|
|
+ loaded_dict['keys'] = start[NS_KEYS].map((x) => parseInt(x['UID']))
|
|
|
+ }
|
|
|
+
|
|
|
+ return loaded_dict
|
|
|
+}
|
|
|
+
|
|
|
+const get_classname_at = (start, pushstore) => {
|
|
|
+ // Get the classname of the object referenced
|
|
|
+ try {
|
|
|
+ return pushstore.objects_list[parseInt(start[CLASS_KEY]['UID'])][CLASSNAME_KEY]
|
|
|
+ } catch (e) {
|
|
|
+ throw e
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const make_entry_dict = (pushstore, loaded) => {
|
|
|
+ // Make dict from offset and keys
|
|
|
+ let entries = {}
|
|
|
+ let offsets = loaded['objects']
|
|
|
+ let keys = loaded['keys']
|
|
|
+
|
|
|
+ let i = 0
|
|
|
+ while (i < keys.length) {
|
|
|
+ entries[pushstore.objects_list[keys[i]]] = pushstore.objects_list[offsets[i]]
|
|
|
+ i++
|
|
|
+ }
|
|
|
+ return entries
|
|
|
+}
|
|
|
+
|
|
|
+const format_entry = (pushstore, entry_dict) => {
|
|
|
+ // Format each of the entries
|
|
|
+ let formatted = {}
|
|
|
+ formatted['AppNotificationCreationDate'] = safe_get_time(pushstore, entry_dict, 'AppNotificationCreationDate')
|
|
|
+ formatted['RequestedDate'] = safe_get_time(pushstore, entry_dict, 'RequestedDate')
|
|
|
+ formatted['TriggerDate'] = safe_get_time(pushstore, entry_dict, 'TriggerDate')
|
|
|
+ formatted['AppNotificationMessage'] = entry_dict['AppNotificationMessage']
|
|
|
+ formatted['AppNotificationTitle'] = entry_dict['AppNotificationTitle'] ? entry_dict['AppNotificationTitle'] : 'N/A'
|
|
|
+
|
|
|
+ return formatted
|
|
|
+}
|
|
|
+
|
|
|
+const safe_get_time = (pushstore, in_dict, key) => {
|
|
|
+ // Safely get a timestamp
|
|
|
+ try {
|
|
|
+ if (in_dict && in_dict[key] && in_dict[key][NS_TIME])
|
|
|
+ return to_real_time(in_dict[key][NS_TIME])
|
|
|
+ return 'N/A'
|
|
|
+ } catch (e) {
|
|
|
+ throw e
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const to_real_time = (ns_time) => {
|
|
|
+ // Convert an NSTime to UTC timestamp string
|
|
|
+ return new Date((ns_time + UNIX_OFFSET) * 1000).toISOString()
|
|
|
+}
|
|
|
+
|
|
|
+module.exports = {
|
|
|
+ run: run
|
|
|
+}
|