Skip to content

Commit

Permalink
Added parser for Mac OS backgrounditems.btm bookmark data
Browse files Browse the repository at this point in the history
  • Loading branch information
joachimmetz committed Feb 4, 2024
1 parent a28171c commit 2373ddc
Show file tree
Hide file tree
Showing 22 changed files with 1,365 additions and 140 deletions.
690 changes: 690 additions & 0 deletions documentation/Mac OS background item bookmark data format.asciidoc

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions documentation/Mac OS login item alias data format.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ alias data format specification.
[cols="1,5"]
|===
| Author(s): | Joachim Metz <[email protected]>
| Abstract: | MacOS Finder Alias and Bookmark file format specification
| Abstract: | Mac OS login item alias data format specification
| Classification: | Public
| Keywords: | Alias, Bookmark
| Keywords: | Login item, Alias, AliasData
|===

[preface]
Expand Down Expand Up @@ -56,7 +56,7 @@ This document used Mac OS to refer to the Macintosh Operating System in general,
instead of specific versions like Mac OS X or macOS. Mac OS X is used to refer
to version of Mac OS 10.0 or later.

The alias data is stored in the property of the following keys:
The alias data is stored in the values of property with the following keys:

....
/SessionItems/CustomListItems/CustomItemProperties/Alias
Expand All @@ -67,8 +67,8 @@ The alias data is stored in the property of the following keys:
|===
| Characteristics | Description
| Byte order | Big-endian
| Date and time values | HFS+ date and time
| Character strings |
| Date and time values | HFS+ date and time in seconds or 65536ths of seconds since January 1, 1904 at 00:00:00 (midnight) UTC
| Character strings | Unicode strings are stored in UTF-8 or UTF-16 big-endian without the byte order mark (BOM).
|===

=== Test version
Expand Down
2 changes: 1 addition & 1 deletion dtformats/alias_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def ReadFileObject(self, file_object):
record_offset = 8

if record_header.record_size != self._file_size:
raise errors.ParseError('Unsupported AliasData record size')
raise errors.ParseError('Unsupported alias data record size')

if record_header.format_version == 3:
_ = self._ReadRecordV3(file_object, record_offset)
Expand Down
13 changes: 1 addition & 12 deletions dtformats/asl.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class AppleSystemLogFile(data_format.BinaryDataFile):
'asl.debug.yaml', custom_format_callbacks={
'posix_time': '_FormatIntegerAsPosixTime',
'record_flags': '_FormatRecordFlags',
'signature': '_FormatStreamAsSignature'})
'signature': '_FormatStreamAsString'})

# Most significant bit of a 64-bit string offset.
_STRING_OFFSET_MSB = 1 << 63
Expand All @@ -32,17 +32,6 @@ def _FormatRecordFlags(self, integer):
"""
return f'0x{integer:04x}'

def _FormatStreamAsSignature(self, stream):
"""Formats a stream as a signature.
Args:
stream (bytes): stream.
Returns:
str: stream formatted as a signature.
"""
return stream.decode('ascii').replace('\x00', '\\x00')

def _ReadFileHeader(self, file_object):
"""Reads the file header.
Expand Down
61 changes: 61 additions & 0 deletions dtformats/bookmark_data.debug.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# dtFormats debug specification.
---
data_type_map: bookmark_data_header
attributes:
- name: signature
description: "Signature"
format: custom:signature
- name: size
description: "Size"
format: decimal
- name: unknown1
description: "Unknown1"
format: hexadecimal_8digits
- name: data_area_offset
description: "Data area offset"
format: hexadecimal_8digits
- name: unknown3
description: "Unknown3"
format: binary_data
---
data_type_map: bookmark_data_record
attributes:
- name: data_size
description: "Data size"
format: decimal
- name: data_type
description: "Data type"
format: hexadecimal_8digits
- name: data
description: "Data"
format: binary_data
- name: floating_point
description: "Floating point"
format: floating_point
- name: integer
description: "Integer"
format: decimal
- name: string
description: "String"
format: string
---
data_type_map: bookmark_data_toc
attributes:
- name: entries_data_size
description: "Entries data size"
format: decimal
- name: unknown1
description: "Unknown1"
format: hexadecimal_8digits
- name: identifier
description: "Identifier"
format: decimal
- name: next_toc_offset
description: "Next TOC offset"
format: hexadecimal_8digits
- name: number_of_tagged_values
description: "Number of tagged values"
format: decimal
- name: tagged_values
description: "Tagged values"
format: custom:array_of_tagged_values
157 changes: 157 additions & 0 deletions dtformats/bookmark_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# -*- coding: utf-8 -*-
"""Mac OS backgrounditems.btm bookmark data."""

from dtformats import data_format
from dtformats import errors


class MacOSBackgroundItemBookmarkData(data_format.BinaryDataFile):
"""Mac OS backgrounditems.btm bookmark data."""

# Using a class constant significantly speeds up the time required to load
# the dtFabric and dtFormats definition files.
_FABRIC = data_format.BinaryDataFile.ReadDefinitionFile('bookmark_data.yaml')

_DEBUG_INFORMATION = data_format.BinaryDataFile.ReadDebugInformationFile(
'bookmark_data.debug.yaml', custom_format_callbacks={
'array_of_tagged_values': '_FormatArrayOfTaggedValues',
'signature': '_FormatStreamAsString'})

def __init__(self, debug=False, output_writer=None):
"""Initializes Mac OS backgrounditems.btm bookmark data.
Args:
debug (Optional[bool]): True if debug information should be written.
output_writer (Optional[OutputWriter]): output writer.
"""
super(MacOSBackgroundItemBookmarkData, self).__init__(
debug=debug, output_writer=output_writer)

def _FormatArrayOfTaggedValues(self, array):
"""Formats an array of tagged values.
Args:
array (list[bookmark_data_tagged_value]): array of tagged values.
Returns:
str: formatted array of tagged values.
"""
value = '\n'.join([
(f'\ttag: 0x{entry.value_tag:04x}, '
f'data record offset: 0x{entry.value_data_record_offset:08x}, '
f'unknown1: 0x{entry.unknown1:08x}')
for entry in array])
return f'{value:s}\n'

def _ReadDataRecord(self, file_object, file_offset):
"""Reads a data record.
Args:
file_object (file): file-like object.
file_offset (int): offset of the data record relative to the start of the
file.
Returns:
bookmark_data_record: data record.
Raises:
ParseError: if the data record cannot be read.
"""
data_type_map = self._GetDataTypeMap('bookmark_data_record')

data_record, _ = self._ReadStructureFromFileObject(
file_object, file_offset, data_type_map, 'data record')

if self._debug:
debug_info = self._DEBUG_INFORMATION.get('bookmark_data_record', None)
self._DebugPrintStructureObject(data_record, debug_info)

return data_record

def _ReadHeader(self, file_object, file_offset):
"""Reads a header.
Args:
file_object (file): file-like object.
file_offset (int): offset of the header relative to the start of the file.
Returns:
bookmark_data_header: header.
Raises:
ParseError: if the header cannot be read.
"""
data_type_map = self._GetDataTypeMap('bookmark_data_header')

header, _ = self._ReadStructureFromFileObject(
file_object, file_offset, data_type_map, 'header')

if self._debug:
debug_info = self._DEBUG_INFORMATION.get('bookmark_data_header', None)
self._DebugPrintStructureObject(header, debug_info)

return header

def _ReadTableOfContents(self, file_object, file_offset):
"""Reads a table of contents (TOC).
Args:
file_object (file): file-like object.
file_offset (int): offset of the table of contents (TOC) relative to the
start of the file.
Returns:
bookmark_data_toc: table of contents (TOC).
Raises:
ParseError: if the table of contents (TOC) cannot be read.
"""
data_type_map = self._GetDataTypeMap('bookmark_data_toc')

table_of_contents, _ = self._ReadStructureFromFileObject(
file_object, file_offset, data_type_map, 'table of contents (TOC)')

if self._debug:
debug_info = self._DEBUG_INFORMATION.get('bookmark_data_toc', None)
self._DebugPrintStructureObject(table_of_contents, debug_info)

if table_of_contents.next_toc_offset != 0:
raise errors.ParseError('Unsupported next TOC offset')

return table_of_contents

def ReadFileObject(self, file_object):
"""Reads a Mac OS backgrounditems.btm bookmark data file-like object.
Args:
file_object (file): file-like object.
Raises:
ParseError: if the file cannot be read.
"""
header = self._ReadHeader(file_object, 0)

if header.size != self._file_size:
raise errors.ParseError('Unsupported bookmark data size')

if header.data_area_offset != 48:
raise errors.ParseError('Unsupported bookmark data area offset')

data_type_map = self._GetDataTypeMap('uint32le')

data_area_size, _ = self._ReadStructureFromFileObject(
file_object, 48, data_type_map, 'data area size')

if self._debug:
value_string, _ = self._FormatIntegerAsDecimal(data_area_size)
self._DebugPrintValue('Data area size', value_string)

self._DebugPrintText('\n')

table_of_contents = self._ReadTableOfContents(
file_object, 48 + data_area_size)

for tagged_value in table_of_contents.tagged_values:
data_record_offset = (
header.data_area_offset + tagged_value.value_data_record_offset)
self._ReadDataRecord(file_object, data_record_offset)
Loading

0 comments on commit 2373ddc

Please sign in to comment.