mirror of
https://github.com/wader/fq.git
synced 2024-11-23 09:56:07 +03:00
apple bookmarkdata decoder initial commit
This commit is contained in:
parent
1dd9c8759a
commit
71b17d0382
276
format/bookmark/bookmark.go
Normal file
276
format/bookmark/bookmark.go
Normal file
@ -0,0 +1,276 @@
|
||||
package bplist
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"time"
|
||||
|
||||
"github.com/wader/fq/format"
|
||||
"github.com/wader/fq/pkg/decode"
|
||||
"github.com/wader/fq/pkg/interp"
|
||||
"github.com/wader/fq/pkg/scalar"
|
||||
)
|
||||
|
||||
//go:embed bplist.jq bplist.md
|
||||
var bookmarkFS embed.FS
|
||||
|
||||
func init() {
|
||||
interp.RegisterFormat(decode.Format{
|
||||
Name: format.BOOKMARK,
|
||||
ProbeOrder: format.ProbeOrderBinUnique,
|
||||
Description: "Apple BookmarkData",
|
||||
Groups: []string{format.PROBE},
|
||||
DecodeFn: bookmarkDecode,
|
||||
Functions: []string{},
|
||||
//Functions: []string{"torepr"},
|
||||
})
|
||||
interp.RegisterFS(bookmarkFS)
|
||||
}
|
||||
|
||||
const (
|
||||
dataTypeString = 0x0101
|
||||
dataTypeData = 0x0201
|
||||
dataTypeNumber8 = 0x0301
|
||||
dataTypeNumber16 = 0x0302
|
||||
dataTypeNumber32 = 0x0303
|
||||
dataTypeNumber64 = 0x0304
|
||||
dataTypeNumber32F = 0x0305
|
||||
dataTypeNumber64F = 0x0306
|
||||
dataTypeDate = 0x0400
|
||||
dataTypeBooleanFalse = 0x0500
|
||||
dataTypeBooleanTrue = 0x0501
|
||||
dataTypeArray = 0x0601
|
||||
dataTypeDictionary = 0x0701
|
||||
dataTypeUUID = 0x0801
|
||||
dataTypeURL = 0x0901
|
||||
dataTypeRelativeURL = 0x0902
|
||||
)
|
||||
|
||||
var dataTypeMap = scalar.UToScalar{
|
||||
dataTypeString: {Sym: "String", Description: "UTF-8 String"},
|
||||
dataTypeData: {Sym: "Data", Description: "Raw bytes"},
|
||||
dataTypeNumber8: {Sym: "Byte", Description: "(signed 8-bit) 1-byte number"},
|
||||
dataTypeNumber16: {Sym: "Short", Description: "(signed 16-bit) 2-byte number"},
|
||||
dataTypeNumber32: {Sym: "Int", Description: "(signed 32-bit) 4-byte number"},
|
||||
dataTypeNumber64: {Sym: "Long", Description: "(signed 64-bit) 8-byte number"},
|
||||
dataTypeNumber32F: {Sym: "Float", Description: "(32-bit float) IEEE single precision"},
|
||||
dataTypeNumber64F: {Sym: "Double", Description: "(64-bit float) IEEE double precision"},
|
||||
dataTypeDate: {Sym: "Date", Description: "Big-endian IEEE double precision seconds since 2001-01-01 00:00:00 UTC"},
|
||||
dataTypeBooleanFalse: {Sym: "BooleanFalse", Description: "(false)"},
|
||||
dataTypeBooleanTrue: {Sym: "BooleanTrue", Description: "(true)"},
|
||||
dataTypeArray: {Sym: "Array", Description: "Array of 4-byte offsets to data items"},
|
||||
dataTypeDictionary: {Sym: "Dictionary", Description: "Array of pairs of 4-byte (key, value) data item offsets"},
|
||||
dataTypeUUID: {Sym: "UUID", Description: "Raw bytes"},
|
||||
dataTypeURL: {Sym: "URL", Description: "UTF-8 string"},
|
||||
dataTypeRelativeURL: {Sym: "RelativeURL", Description: "4-byte offset to base URL, 4-byte offset to UTF-8 string"},
|
||||
}
|
||||
|
||||
const (
|
||||
elementTypeTargetURL = 0x1003
|
||||
elementTypeTargetPath = 0x1004
|
||||
elementTypeTargetCNIDPath = 0x1005
|
||||
elementTypeTargetFlags = 0x1010
|
||||
elementTypeTargetFilename = 0x1020
|
||||
elementTypeCNID = 0x1030
|
||||
elementTypeTargetCreationDate = 0x1040
|
||||
elementTypeUnknown1 = 0x1054
|
||||
elementTypeUnknown2 = 0x1055
|
||||
elementTypeUnknown3 = 0x1056
|
||||
elementTypeUnknown4 = 0x1101
|
||||
elementTypeUnknown5 = 0x1102
|
||||
elementTypeTOCPath = 0x2000
|
||||
elementTypeVolumePath = 0x2002
|
||||
elementTypeVolumeURL = 0x2005
|
||||
elementTypeVolumeName = 0x2010
|
||||
elementTypeVolumeUUID = 0x2011
|
||||
elementTypeVolumeSize = 0x2012
|
||||
elementTypeVolumeCreationDate = 0x2013
|
||||
elementTypeVolumeFlags = 0x2020
|
||||
elementTypeVolumeIsRoot = 0x2030
|
||||
elementTypeVolumeBookmark = 0x2040
|
||||
elementTypeVolumeMountPointURL = 0x2050
|
||||
elementTypeUnknown6 = 0x2070
|
||||
elementTypeContainingFolderIndex = 0xc001
|
||||
elementTypeCreatorUsername = 0xc011
|
||||
elementTypeCreatorUID = 0xc012
|
||||
elementTypeFileReferenceFlag = 0xd001
|
||||
elementTypeCreationOptions = 0xd010
|
||||
elementTypeURLLengthArray = 0xe003
|
||||
elementTypeDisplayName = 0xf017
|
||||
elementTypeIconData = 0xf020
|
||||
elementTypeIconImageData = 0xf021
|
||||
elementTypeTypeBindingInfo = 0xf022
|
||||
elementTypeBookmarkCreationTime = 0xf030
|
||||
elementTypeSandboxRWExtension = 0xf080
|
||||
elementTypeSandboxROExtension = 0xf081
|
||||
)
|
||||
|
||||
var elementTypeMap = scalar.UToScalar{
|
||||
elementTypeTargetURL: {Sym: "Target URL", Description: "A URL"},
|
||||
elementTypeTargetPath: {Sym: "Target path", Description: "Array of individual path components"},
|
||||
elementTypeTargetCNIDPath: {Sym: "Target CNID path", Description: "Array of CNIDs"},
|
||||
elementTypeTargetFlags: {Sym: "Target flags", Description: "Data - see below"},
|
||||
elementTypeTargetFilename: {Sym: "Target filename", Description: "String"},
|
||||
elementTypeCNID: {Sym: "Target CNID", Description: "4-byte integer"},
|
||||
elementTypeTargetCreationDate: {Sym: "Target creation date", Description: "Date"},
|
||||
elementTypeUnknown1: {Sym: "Unknown", Description: "Unknown"},
|
||||
elementTypeUnknown2: {Sym: "Unknown", Description: "Unknown"},
|
||||
elementTypeUnknown3: {Sym: "Unknown", Description: "Unknown"},
|
||||
elementTypeUnknown4: {Sym: "Unknown", Description: "Unknown"},
|
||||
elementTypeUnknown5: {Sym: "Unknown", Description: "Unknown"},
|
||||
elementTypeTOCPath: {Sym: "TOC path", Description: "Array - see below"},
|
||||
elementTypeVolumePath: {Sym: "Volume path", Description: "Array of individual path components"},
|
||||
elementTypeVolumeURL: {Sym: "Volume URL", Description: "URL of volume root"},
|
||||
elementTypeVolumeName: {Sym: "Volume name", Description: "String"},
|
||||
elementTypeVolumeUUID: {Sym: "Volume UUID", Description: "String (not a UUID!)"},
|
||||
elementTypeVolumeSize: {Sym: "Volume size", Description: "8-byte integer"},
|
||||
elementTypeVolumeCreationDate: {Sym: "Volume creation date", Description: "Date"},
|
||||
elementTypeVolumeFlags: {Sym: "Volume flags", Description: "Data - see below"},
|
||||
elementTypeVolumeIsRoot: {Sym: "Volume is root", Description: "True if the volume was the filesystem root"},
|
||||
elementTypeVolumeBookmark: {Sym: "Volume bookmark", Description: "TOC identifier for disk image"},
|
||||
elementTypeVolumeMountPointURL: {Sym: "Volume mount point", Description: "URL"},
|
||||
elementTypeUnknown6: {Sym: "Unknown", Description: "Unknown"},
|
||||
elementTypeContainingFolderIndex: {Sym: "Containing folder index", Description: "Integer index of containing folder in target path array"},
|
||||
elementTypeCreatorUsername: {Sym: "Creator username", Description: "Name of user that created bookmark"},
|
||||
elementTypeCreatorUID: {Sym: "Creator UID", Description: "UID of user that created bookmark"},
|
||||
elementTypeFileReferenceFlag: {Sym: "File reference flag", Description: "True if creating URL was a file reference URL"},
|
||||
elementTypeCreationOptions: {Sym: "Creation options", Description: "Integer containing flags passed to CFURLCreateBookmarkData"},
|
||||
elementTypeURLLengthArray: {Sym: "URL length array", Description: "Array of integers - see below"},
|
||||
elementTypeDisplayName: {Sym: "Display name", Description: "String"},
|
||||
elementTypeIconData: {Sym: "Icon data", Description: "icns format data"},
|
||||
elementTypeIconImageData: {Sym: "Icon image", Description: "Data"},
|
||||
elementTypeTypeBindingInfo: {Sym: "Type binding info", Description: "dnib byte array"},
|
||||
elementTypeBookmarkCreationTime: {Sym: "Bookmark creation time", Description: "64-bit float seconds since January 1st 2001"},
|
||||
elementTypeSandboxRWExtension: {Sym: "Sandbox RW extension", Description: "Looks like a hash with data and an access right"},
|
||||
elementTypeSandboxROExtension: {Sym: "Sandbox RO extension", Description: "As above"},
|
||||
}
|
||||
|
||||
var cocoaTimeEpochDate = time.Date(2001, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
type tocHeader struct {
|
||||
tocSize uint64
|
||||
nextTOCOffset uint64
|
||||
numEntries uint64
|
||||
}
|
||||
|
||||
func decodeTOCHeader(d *decode.D) *tocHeader {
|
||||
var hdr *tocHeader
|
||||
|
||||
d.FieldStruct("first_toc", func(d *decode.D) {
|
||||
hdr.tocSize = d.FieldU32("toc_size")
|
||||
d.FieldU32("magic", d.AssertU(0xfffffffe))
|
||||
d.FieldU32("identifier")
|
||||
hdr.nextTOCOffset = d.FieldU32("next_toc_offset")
|
||||
hdr.numEntries = d.FieldU32("num_entries_in_toc")
|
||||
})
|
||||
|
||||
return hdr
|
||||
}
|
||||
|
||||
type tocEntry struct {
|
||||
key uint64
|
||||
recordOffset uint64
|
||||
}
|
||||
|
||||
func decodeTOCEntry(d *decode.D) *tocEntry {
|
||||
var entry *tocEntry
|
||||
|
||||
entry.key = d.FieldU32("key")
|
||||
entry.recordOffset = d.FieldU32("offset_to_record")
|
||||
d.FieldU32("reserved")
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
const (
|
||||
arrayEntrySize = 4
|
||||
dictEntrySize = 4
|
||||
)
|
||||
|
||||
func decodeRecord(d *decode.D, offset uint64) {
|
||||
d.FieldStruct("record", func(d *decode.D) {
|
||||
n := int(d.FieldU32("length"))
|
||||
typ := d.FieldU32("type", dataTypeMap)
|
||||
switch typ {
|
||||
case dataTypeString:
|
||||
d.FieldUTF8("data", n)
|
||||
case dataTypeData:
|
||||
d.FieldRawLen("data", int64(n*8))
|
||||
case dataTypeNumber8:
|
||||
d.FieldS8("data")
|
||||
case dataTypeNumber16:
|
||||
d.FieldS16("data")
|
||||
case dataTypeNumber32:
|
||||
d.FieldS32("data")
|
||||
case dataTypeNumber64:
|
||||
d.FieldS64("data")
|
||||
case dataTypeNumber32F:
|
||||
d.FieldF32("data")
|
||||
case dataTypeNumber64F:
|
||||
d.FieldF64("data")
|
||||
case dataTypeDate:
|
||||
d.FieldF64("data")
|
||||
case dataTypeBooleanFalse:
|
||||
case dataTypeBooleanTrue:
|
||||
case dataTypeArray:
|
||||
d.FieldStructNArray("data", "element", int64(n/arrayEntrySize), func(d *decode.D) {
|
||||
offset := d.FieldU32("offset")
|
||||
decodeRecord(d, offset)
|
||||
})
|
||||
case dataTypeDictionary:
|
||||
d.FieldStructNArray("data", "element", int64(n/dictEntrySize), func(d *decode.D) {
|
||||
keyOffset := d.FieldU32("key_offset")
|
||||
d.FieldStruct("key", func(d *decode.D) {
|
||||
decodeRecord(d, keyOffset)
|
||||
})
|
||||
|
||||
valueOffset := d.FieldU32("value_offset")
|
||||
d.FieldStruct("key", func(d *decode.D) {
|
||||
decodeRecord(d, valueOffset)
|
||||
})
|
||||
})
|
||||
case dataTypeUUID:
|
||||
d.FieldRawLen("data", int64(n*8))
|
||||
case dataTypeURL:
|
||||
d.FieldUTF8("data", n)
|
||||
case dataTypeRelativeURL:
|
||||
baseOffset := d.FieldU32("base_url_offset")
|
||||
d.FieldStruct("base_url", func(d *decode.D) {
|
||||
decodeRecord(d, baseOffset)
|
||||
})
|
||||
|
||||
suffixOffset := d.FieldU32("suffix_offset")
|
||||
d.FieldStruct("suffix", func(d *decode.D) {
|
||||
decodeRecord(d, suffixOffset)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func bookmarkDecode(d *decode.D, _ any) any {
|
||||
d.FieldStruct("header", func(d *decode.D) {
|
||||
d.FieldUTF8("magic", 4, d.AssertStr("book", "alis"))
|
||||
d.FieldU32("total_size")
|
||||
d.FieldU32("unknown")
|
||||
d.FieldU32("header_size", d.AssertU(48))
|
||||
})
|
||||
|
||||
tocOffset := d.FieldU32("first_toc_offset")
|
||||
|
||||
for tocOffset != 0 {
|
||||
d.SeekAbs(int64(tocOffset), func(d *decode.D) {
|
||||
tocHdr := decodeTOCHeader(d)
|
||||
tocOffset = tocHdr.nextTOCOffset
|
||||
d.FieldStructNArray("entries", "entry", int64(tocHdr.numEntries), func(d *decode.D) {
|
||||
var entry *tocEntry
|
||||
|
||||
entry.key = d.FieldU32("key", elementTypeMap)
|
||||
entry.recordOffset = d.FieldU32("offset_to_record")
|
||||
d.FieldU32("reserved")
|
||||
|
||||
decodeRecord(d, entry.recordOffset)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -49,6 +49,7 @@ const (
|
||||
BITCOIN_BLOCK = "bitcoin_block"
|
||||
BITCOIN_SCRIPT = "bitcoin_script"
|
||||
BITCOIN_TRANSACTION = "bitcoin_transaction"
|
||||
BOOKMARK = "apple_bookmarkdata"
|
||||
BPLIST = "bplist"
|
||||
BSD_LOOPBACK_FRAME = "bsd_loopback_frame"
|
||||
BSON = "bson"
|
||||
|
Loading…
Reference in New Issue
Block a user