dmg-builder 26.5.0 → 26.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/dmg.d.ts +2 -0
- package/out/dmg.js.map +1 -1
- package/out/dmgUtil.d.ts +2 -3
- package/out/dmgUtil.js +43 -23
- package/out/dmgUtil.js.map +1 -1
- package/package.json +3 -4
- package/vendor/biplist/__init__.py +0 -977
- package/vendor/dmgbuild/__init__.py +0 -5
- package/vendor/dmgbuild/__main__.py +0 -70
- package/vendor/dmgbuild/badge.py +0 -169
- package/vendor/dmgbuild/colors.py +0 -504
- package/vendor/dmgbuild/core.py +0 -963
- package/vendor/dmgbuild/licensing.py +0 -331
- package/vendor/dmgbuild/resources/builtin-arrow.tiff +0 -0
- package/vendor/ds_store/__init__.py +0 -5
- package/vendor/ds_store/__main__.py +0 -102
- package/vendor/ds_store/buddy.py +0 -486
- package/vendor/ds_store/store.py +0 -1262
- package/vendor/mac_alias/__init__.py +0 -47
- package/vendor/mac_alias/alias.py +0 -772
- package/vendor/mac_alias/bookmark.py +0 -677
- package/vendor/mac_alias/osx.py +0 -1074
- package/vendor/mac_alias/utils.py +0 -20
- package/vendor/run_dmgbuild.py +0 -14
|
@@ -1,677 +0,0 @@
|
|
|
1
|
-
# This file implements the Apple "bookmark" format, which is the replacement
|
|
2
|
-
# for the old-fashioned alias format. The details of this format were
|
|
3
|
-
# reverse engineered; some things are still not entirely clear.
|
|
4
|
-
#
|
|
5
|
-
import datetime
|
|
6
|
-
import os
|
|
7
|
-
import struct
|
|
8
|
-
import sys
|
|
9
|
-
import uuid
|
|
10
|
-
from urllib.parse import urljoin
|
|
11
|
-
|
|
12
|
-
if sys.platform == "darwin":
|
|
13
|
-
from . import osx
|
|
14
|
-
|
|
15
|
-
from .utils import osx_epoch
|
|
16
|
-
|
|
17
|
-
BMK_DATA_TYPE_MASK = 0xFFFFFF00
|
|
18
|
-
BMK_DATA_SUBTYPE_MASK = 0x000000FF
|
|
19
|
-
|
|
20
|
-
BMK_STRING = 0x0100
|
|
21
|
-
BMK_DATA = 0x0200
|
|
22
|
-
BMK_NUMBER = 0x0300
|
|
23
|
-
BMK_DATE = 0x0400
|
|
24
|
-
BMK_BOOLEAN = 0x0500
|
|
25
|
-
BMK_ARRAY = 0x0600
|
|
26
|
-
BMK_DICT = 0x0700
|
|
27
|
-
BMK_UUID = 0x0800
|
|
28
|
-
BMK_URL = 0x0900
|
|
29
|
-
BMK_NULL = 0x0A00
|
|
30
|
-
|
|
31
|
-
BMK_ST_ZERO = 0x0000
|
|
32
|
-
BMK_ST_ONE = 0x0001
|
|
33
|
-
|
|
34
|
-
BMK_BOOLEAN_ST_FALSE = 0x0000
|
|
35
|
-
BMK_BOOLEAN_ST_TRUE = 0x0001
|
|
36
|
-
|
|
37
|
-
# Subtypes for BMK_NUMBER are really CFNumberType values
|
|
38
|
-
kCFNumberSInt8Type = 1
|
|
39
|
-
kCFNumberSInt16Type = 2
|
|
40
|
-
kCFNumberSInt32Type = 3
|
|
41
|
-
kCFNumberSInt64Type = 4
|
|
42
|
-
kCFNumberFloat32Type = 5
|
|
43
|
-
kCFNumberFloat64Type = 6
|
|
44
|
-
kCFNumberCharType = 7
|
|
45
|
-
kCFNumberShortType = 8
|
|
46
|
-
kCFNumberIntType = 9
|
|
47
|
-
kCFNumberLongType = 10
|
|
48
|
-
kCFNumberLongLongType = 11
|
|
49
|
-
kCFNumberFloatType = 12
|
|
50
|
-
kCFNumberDoubleType = 13
|
|
51
|
-
kCFNumberCFIndexType = 14
|
|
52
|
-
kCFNumberNSIntegerType = 15
|
|
53
|
-
kCFNumberCGFloatType = 16
|
|
54
|
-
|
|
55
|
-
# Resource property flags (from CFURLPriv.h)
|
|
56
|
-
kCFURLResourceIsRegularFile = 0x00000001
|
|
57
|
-
kCFURLResourceIsDirectory = 0x00000002
|
|
58
|
-
kCFURLResourceIsSymbolicLink = 0x00000004
|
|
59
|
-
kCFURLResourceIsVolume = 0x00000008
|
|
60
|
-
kCFURLResourceIsPackage = 0x00000010
|
|
61
|
-
kCFURLResourceIsSystemImmutable = 0x00000020
|
|
62
|
-
kCFURLResourceIsUserImmutable = 0x00000040
|
|
63
|
-
kCFURLResourceIsHidden = 0x00000080
|
|
64
|
-
kCFURLResourceHasHiddenExtension = 0x00000100
|
|
65
|
-
kCFURLResourceIsApplication = 0x00000200
|
|
66
|
-
kCFURLResourceIsCompressed = 0x00000400
|
|
67
|
-
kCFURLResourceIsSystemCompressed = 0x00000400
|
|
68
|
-
kCFURLCanSetHiddenExtension = 0x00000800
|
|
69
|
-
kCFURLResourceIsReadable = 0x00001000
|
|
70
|
-
kCFURLResourceIsWriteable = 0x00002000
|
|
71
|
-
kCFURLResourceIsExecutable = 0x00004000
|
|
72
|
-
kCFURLIsAliasFile = 0x00008000
|
|
73
|
-
kCFURLIsMountTrigger = 0x00010000
|
|
74
|
-
|
|
75
|
-
# Volume property flags (from CFURLPriv.h)
|
|
76
|
-
kCFURLVolumeIsLocal = 0x1
|
|
77
|
-
kCFURLVolumeIsAutomount = 0x2
|
|
78
|
-
kCFURLVolumeDontBrowse = 0x4
|
|
79
|
-
kCFURLVolumeIsReadOnly = 0x8
|
|
80
|
-
kCFURLVolumeIsQuarantined = 0x10
|
|
81
|
-
kCFURLVolumeIsEjectable = 0x20
|
|
82
|
-
kCFURLVolumeIsRemovable = 0x40
|
|
83
|
-
kCFURLVolumeIsInternal = 0x80
|
|
84
|
-
kCFURLVolumeIsExternal = 0x100
|
|
85
|
-
kCFURLVolumeIsDiskImage = 0x200
|
|
86
|
-
kCFURLVolumeIsFileVault = 0x400
|
|
87
|
-
kCFURLVolumeIsLocaliDiskMirror = 0x800
|
|
88
|
-
kCFURLVolumeIsiPod = 0x1000
|
|
89
|
-
kCFURLVolumeIsiDisk = 0x2000
|
|
90
|
-
kCFURLVolumeIsCD = 0x4000
|
|
91
|
-
kCFURLVolumeIsDVD = 0x8000
|
|
92
|
-
kCFURLVolumeIsDeviceFileSystem = 0x10000
|
|
93
|
-
kCFURLVolumeSupportsPersistentIDs = 0x100000000
|
|
94
|
-
kCFURLVolumeSupportsSearchFS = 0x200000000
|
|
95
|
-
kCFURLVolumeSupportsExchange = 0x400000000
|
|
96
|
-
# reserved 0x800000000
|
|
97
|
-
kCFURLVolumeSupportsSymbolicLinks = 0x1000000000
|
|
98
|
-
kCFURLVolumeSupportsDenyModes = 0x2000000000
|
|
99
|
-
kCFURLVolumeSupportsCopyFile = 0x4000000000
|
|
100
|
-
kCFURLVolumeSupportsReadDirAttr = 0x8000000000
|
|
101
|
-
kCFURLVolumeSupportsJournaling = 0x10000000000
|
|
102
|
-
kCFURLVolumeSupportsRename = 0x20000000000
|
|
103
|
-
kCFURLVolumeSupportsFastStatFS = 0x40000000000
|
|
104
|
-
kCFURLVolumeSupportsCaseSensitiveNames = 0x80000000000
|
|
105
|
-
kCFURLVolumeSupportsCasePreservedNames = 0x100000000000
|
|
106
|
-
kCFURLVolumeSupportsFLock = 0x200000000000
|
|
107
|
-
kCFURLVolumeHasNoRootDirectoryTimes = 0x400000000000
|
|
108
|
-
kCFURLVolumeSupportsExtendedSecurity = 0x800000000000
|
|
109
|
-
kCFURLVolumeSupports2TBFileSize = 0x1000000000000
|
|
110
|
-
kCFURLVolumeSupportsHardLinks = 0x2000000000000
|
|
111
|
-
kCFURLVolumeSupportsMandatoryByteRangeLocks = 0x4000000000000
|
|
112
|
-
kCFURLVolumeSupportsPathFromID = 0x8000000000000
|
|
113
|
-
# reserved 0x10000000000000
|
|
114
|
-
kCFURLVolumeIsJournaling = 0x20000000000000
|
|
115
|
-
kCFURLVolumeSupportsSparseFiles = 0x40000000000000
|
|
116
|
-
kCFURLVolumeSupportsZeroRuns = 0x80000000000000
|
|
117
|
-
kCFURLVolumeSupportsVolumeSizes = 0x100000000000000
|
|
118
|
-
kCFURLVolumeSupportsRemoteEvents = 0x200000000000000
|
|
119
|
-
kCFURLVolumeSupportsHiddenFiles = 0x400000000000000
|
|
120
|
-
kCFURLVolumeSupportsDecmpFSCompression = 0x800000000000000
|
|
121
|
-
kCFURLVolumeHas64BitObjectIDs = 0x1000000000000000
|
|
122
|
-
kCFURLVolumePropertyFlagsAll = 0xFFFFFFFFFFFFFFFF
|
|
123
|
-
|
|
124
|
-
BMK_URL_ST_ABSOLUTE = 0x0001
|
|
125
|
-
BMK_URL_ST_RELATIVE = 0x0002
|
|
126
|
-
|
|
127
|
-
# Bookmark keys
|
|
128
|
-
kBookmarkURL = 0x1003 # A URL
|
|
129
|
-
kBookmarkPath = 0x1004 # Array of path components
|
|
130
|
-
kBookmarkCNIDPath = 0x1005 # Array of CNIDs
|
|
131
|
-
kBookmarkFileProperties = (
|
|
132
|
-
0x1010 # (CFURL rp flags, CFURL rp flags asked for, 8 bytes NULL)
|
|
133
|
-
)
|
|
134
|
-
kBookmarkFileName = 0x1020
|
|
135
|
-
kBookmarkFileID = 0x1030
|
|
136
|
-
kBookmarkFileCreationDate = 0x1040
|
|
137
|
-
# = 0x1054 # ?
|
|
138
|
-
# = 0x1055 # ?
|
|
139
|
-
# = 0x1056 # ?
|
|
140
|
-
# = 0x1101 # ?
|
|
141
|
-
# = 0x1102 # ?
|
|
142
|
-
kBookmarkTOCPath = 0x2000 # A list of (TOC id, ?) pairs
|
|
143
|
-
kBookmarkVolumePath = 0x2002
|
|
144
|
-
kBookmarkVolumeURL = 0x2005
|
|
145
|
-
kBookmarkVolumeName = 0x2010
|
|
146
|
-
kBookmarkVolumeUUID = 0x2011 # Stored (perversely) as a string
|
|
147
|
-
kBookmarkVolumeSize = 0x2012
|
|
148
|
-
kBookmarkVolumeCreationDate = 0x2013
|
|
149
|
-
kBookmarkVolumeProperties = (
|
|
150
|
-
0x2020 # (CFURL vp flags, CFURL vp flags asked for, 8 bytes NULL)
|
|
151
|
-
)
|
|
152
|
-
kBookmarkVolumeIsRoot = 0x2030 # True if volume is FS root
|
|
153
|
-
kBookmarkVolumeBookmark = 0x2040 # Embedded bookmark for disk image (TOC id)
|
|
154
|
-
kBookmarkVolumeMountPoint = 0x2050 # A URL
|
|
155
|
-
# = 0x2070
|
|
156
|
-
kBookmarkContainingFolder = 0xC001 # Index of containing folder in path
|
|
157
|
-
kBookmarkUserName = 0xC011 # User that created bookmark
|
|
158
|
-
kBookmarkUID = 0xC012 # UID that created bookmark
|
|
159
|
-
kBookmarkWasFileReference = 0xD001 # True if the URL was a file reference
|
|
160
|
-
kBookmarkCreationOptions = 0xD010
|
|
161
|
-
kBookmarkURLLengths = 0xE003 # See below
|
|
162
|
-
kBookmarkDisplayName = 0xF017
|
|
163
|
-
kBookmarkIconData = 0xF020
|
|
164
|
-
kBookmarkIconRef = 0xF021
|
|
165
|
-
kBookmarkTypeBindingData = 0xF022
|
|
166
|
-
kBookmarkCreationTime = 0xF030
|
|
167
|
-
kBookmarkSandboxRwExtension = 0xF080
|
|
168
|
-
kBookmarkSandboxRoExtension = 0xF081
|
|
169
|
-
kBookmarkAliasData = 0xFE00
|
|
170
|
-
|
|
171
|
-
# Alias for backwards compatibility
|
|
172
|
-
kBookmarkSecurityExtension = kBookmarkSandboxRwExtension
|
|
173
|
-
|
|
174
|
-
# kBookmarkURLLengths is an array that is set if the URL encoded by the
|
|
175
|
-
# bookmark had a base URL; in that case, each entry is the length of the
|
|
176
|
-
# base URL in question. Thus a URL
|
|
177
|
-
#
|
|
178
|
-
# file:///foo/bar/baz blam/blat.html
|
|
179
|
-
#
|
|
180
|
-
# will result in [3, 2], while the URL
|
|
181
|
-
#
|
|
182
|
-
# file:///foo bar/baz blam blat.html
|
|
183
|
-
#
|
|
184
|
-
# would result in [1, 2, 1, 1]
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
class Data:
|
|
188
|
-
def __init__(self, bytedata=None):
|
|
189
|
-
#: The bytes, stored as a byte string
|
|
190
|
-
self.bytes = bytes(bytedata)
|
|
191
|
-
|
|
192
|
-
def __repr__(self):
|
|
193
|
-
return "Data(%r)" % self.bytes
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
class URL:
|
|
197
|
-
def __init__(self, base, rel=None):
|
|
198
|
-
if rel is not None:
|
|
199
|
-
#: The base URL, if any (a :class:`URL`)
|
|
200
|
-
self.base = base
|
|
201
|
-
#: The rest of the URL (a string)
|
|
202
|
-
self.relative = rel
|
|
203
|
-
else:
|
|
204
|
-
self.base = None
|
|
205
|
-
self.relative = base
|
|
206
|
-
|
|
207
|
-
@property
|
|
208
|
-
def absolute(self):
|
|
209
|
-
"""Return an absolute URL."""
|
|
210
|
-
if self.base is None:
|
|
211
|
-
return self.relative
|
|
212
|
-
else:
|
|
213
|
-
return urljoin(self.base.absolute, self.relative)
|
|
214
|
-
|
|
215
|
-
def __repr__(self):
|
|
216
|
-
return "URL(%r)" % self.absolute
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
class Bookmark:
|
|
220
|
-
def __init__(self, tocs=None):
|
|
221
|
-
if tocs is None:
|
|
222
|
-
#: The TOCs for this Bookmark
|
|
223
|
-
self.tocs = []
|
|
224
|
-
else:
|
|
225
|
-
self.tocs = tocs
|
|
226
|
-
|
|
227
|
-
@classmethod
|
|
228
|
-
def _get_item(cls, data, hdrsize, offset):
|
|
229
|
-
offset += hdrsize
|
|
230
|
-
if offset > len(data) - 8:
|
|
231
|
-
raise ValueError("Offset out of range")
|
|
232
|
-
|
|
233
|
-
length, typecode = struct.unpack(b"<II", data[offset : offset + 8])
|
|
234
|
-
|
|
235
|
-
if len(data) - offset < 8 + length:
|
|
236
|
-
raise ValueError("Data item truncated")
|
|
237
|
-
|
|
238
|
-
databytes = data[offset + 8 : offset + 8 + length]
|
|
239
|
-
|
|
240
|
-
dsubtype = typecode & BMK_DATA_SUBTYPE_MASK
|
|
241
|
-
dtype = typecode & BMK_DATA_TYPE_MASK
|
|
242
|
-
|
|
243
|
-
if dtype == BMK_STRING:
|
|
244
|
-
return databytes.decode("utf-8")
|
|
245
|
-
elif dtype == BMK_DATA:
|
|
246
|
-
return Data(databytes)
|
|
247
|
-
elif dtype == BMK_NUMBER:
|
|
248
|
-
if dsubtype == kCFNumberSInt8Type:
|
|
249
|
-
return ord(databytes[0])
|
|
250
|
-
elif dsubtype == kCFNumberSInt16Type:
|
|
251
|
-
return struct.unpack(b"<h", databytes)[0]
|
|
252
|
-
elif dsubtype == kCFNumberSInt32Type:
|
|
253
|
-
return struct.unpack(b"<i", databytes)[0]
|
|
254
|
-
elif dsubtype == kCFNumberSInt64Type:
|
|
255
|
-
return struct.unpack(b"<q", databytes)[0]
|
|
256
|
-
elif dsubtype == kCFNumberFloat32Type:
|
|
257
|
-
return struct.unpack(b"<f", databytes)[0]
|
|
258
|
-
elif dsubtype == kCFNumberFloat64Type:
|
|
259
|
-
return struct.unpack(b"<d", databytes)[0]
|
|
260
|
-
elif dtype == BMK_DATE:
|
|
261
|
-
# Yes, dates really are stored as *BIG-endian* doubles; everything
|
|
262
|
-
# else is little-endian
|
|
263
|
-
secs = datetime.timedelta(seconds=struct.unpack(b">d", databytes)[0])
|
|
264
|
-
return osx_epoch + secs
|
|
265
|
-
elif dtype == BMK_BOOLEAN:
|
|
266
|
-
if dsubtype == BMK_BOOLEAN_ST_TRUE:
|
|
267
|
-
return True
|
|
268
|
-
elif dsubtype == BMK_BOOLEAN_ST_FALSE:
|
|
269
|
-
return False
|
|
270
|
-
elif dtype == BMK_UUID:
|
|
271
|
-
return uuid.UUID(bytes=databytes)
|
|
272
|
-
elif dtype == BMK_URL:
|
|
273
|
-
if dsubtype == BMK_URL_ST_ABSOLUTE:
|
|
274
|
-
return URL(databytes.decode("utf-8"))
|
|
275
|
-
elif dsubtype == BMK_URL_ST_RELATIVE:
|
|
276
|
-
baseoff, reloff = struct.unpack(b"<II", databytes)
|
|
277
|
-
base = cls._get_item(data, hdrsize, baseoff)
|
|
278
|
-
rel = cls._get_item(data, hdrsize, reloff)
|
|
279
|
-
return URL(base, rel)
|
|
280
|
-
elif dtype == BMK_ARRAY:
|
|
281
|
-
result = []
|
|
282
|
-
for aoff in range(offset + 8, offset + 8 + length, 4):
|
|
283
|
-
(eltoff,) = struct.unpack(b"<I", data[aoff : aoff + 4])
|
|
284
|
-
result.append(cls._get_item(data, hdrsize, eltoff))
|
|
285
|
-
return result
|
|
286
|
-
elif dtype == BMK_DICT:
|
|
287
|
-
result = {}
|
|
288
|
-
for eoff in range(offset + 8, offset + 8 + length, 8):
|
|
289
|
-
keyoff, valoff = struct.unpack(b"<II", data[eoff : eoff + 8])
|
|
290
|
-
key = cls._get_item(data, hdrsize, keyoff)
|
|
291
|
-
val = cls._get_item(data, hdrsize, valoff)
|
|
292
|
-
result[key] = val
|
|
293
|
-
return result
|
|
294
|
-
elif dtype == BMK_NULL:
|
|
295
|
-
return None
|
|
296
|
-
|
|
297
|
-
print("Unknown data type %08x" % typecode)
|
|
298
|
-
return (typecode, databytes)
|
|
299
|
-
|
|
300
|
-
@classmethod
|
|
301
|
-
def from_bytes(cls, data):
|
|
302
|
-
"""Create a :class:`Bookmark` given byte data."""
|
|
303
|
-
|
|
304
|
-
if len(data) < 16:
|
|
305
|
-
raise ValueError("Not a bookmark file (too short)")
|
|
306
|
-
|
|
307
|
-
if isinstance(data, bytearray):
|
|
308
|
-
data = bytes(data)
|
|
309
|
-
|
|
310
|
-
magic, size, dummy, hdrsize = struct.unpack(b"<4sIII", data[0:16])
|
|
311
|
-
|
|
312
|
-
if magic not in (b"book", b"alis"):
|
|
313
|
-
raise ValueError("Not a bookmark file (bad magic) %r" % magic)
|
|
314
|
-
|
|
315
|
-
if hdrsize < 16:
|
|
316
|
-
raise ValueError("Not a bookmark file (header size too short)")
|
|
317
|
-
|
|
318
|
-
if hdrsize > size:
|
|
319
|
-
raise ValueError("Not a bookmark file (header size too large)")
|
|
320
|
-
|
|
321
|
-
if size != len(data):
|
|
322
|
-
raise ValueError("Not a bookmark file (truncated)")
|
|
323
|
-
|
|
324
|
-
(tocoffset,) = struct.unpack(b"<I", data[hdrsize : hdrsize + 4])
|
|
325
|
-
|
|
326
|
-
tocs = []
|
|
327
|
-
|
|
328
|
-
while tocoffset != 0:
|
|
329
|
-
tocbase = hdrsize + tocoffset
|
|
330
|
-
if (tocoffset > size - hdrsize) or (size - tocbase < 20):
|
|
331
|
-
raise ValueError("TOC offset out of range")
|
|
332
|
-
|
|
333
|
-
(tocsize, tocmagic, tocid, nexttoc, toccount) = struct.unpack(
|
|
334
|
-
b"<IIIII", data[tocbase : tocbase + 20]
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
if tocmagic != 0xFFFFFFFE:
|
|
338
|
-
break
|
|
339
|
-
|
|
340
|
-
tocsize += 8
|
|
341
|
-
|
|
342
|
-
if size - tocbase < tocsize:
|
|
343
|
-
raise ValueError("TOC truncated")
|
|
344
|
-
|
|
345
|
-
if tocsize < 12 * toccount:
|
|
346
|
-
raise ValueError("TOC entries overrun TOC size")
|
|
347
|
-
|
|
348
|
-
toc = {}
|
|
349
|
-
for n in range(0, toccount):
|
|
350
|
-
ebase = tocbase + 20 + 12 * n
|
|
351
|
-
eid, eoffset, edummy = struct.unpack(b"<III", data[ebase : ebase + 12])
|
|
352
|
-
|
|
353
|
-
if eid & 0x80000000:
|
|
354
|
-
eid = cls._get_item(data, hdrsize, eid & 0x7FFFFFFF)
|
|
355
|
-
|
|
356
|
-
toc[eid] = cls._get_item(data, hdrsize, eoffset)
|
|
357
|
-
|
|
358
|
-
tocs.append((tocid, toc))
|
|
359
|
-
|
|
360
|
-
tocoffset = nexttoc
|
|
361
|
-
|
|
362
|
-
return cls(tocs)
|
|
363
|
-
|
|
364
|
-
def __getitem__(self, key):
|
|
365
|
-
for tid, toc in self.tocs:
|
|
366
|
-
if key in toc:
|
|
367
|
-
return toc[key]
|
|
368
|
-
raise KeyError("Key not found")
|
|
369
|
-
|
|
370
|
-
def __setitem__(self, key, value):
|
|
371
|
-
if len(self.tocs) == 0:
|
|
372
|
-
self.tocs = [(1, {})]
|
|
373
|
-
self.tocs[0][1][key] = value
|
|
374
|
-
|
|
375
|
-
def get(self, key, default=None):
|
|
376
|
-
"""Lookup the value for a given key, returning a default if not
|
|
377
|
-
present."""
|
|
378
|
-
for tid, toc in self.tocs:
|
|
379
|
-
if key in toc:
|
|
380
|
-
return toc[key]
|
|
381
|
-
return default
|
|
382
|
-
|
|
383
|
-
@classmethod
|
|
384
|
-
def _encode_item(cls, item, offset):
|
|
385
|
-
if item is True:
|
|
386
|
-
result = struct.pack(b"<II", 0, BMK_BOOLEAN | BMK_BOOLEAN_ST_TRUE)
|
|
387
|
-
elif item is False:
|
|
388
|
-
result = struct.pack(b"<II", 0, BMK_BOOLEAN | BMK_BOOLEAN_ST_FALSE)
|
|
389
|
-
elif isinstance(item, str):
|
|
390
|
-
encoded = item.encode("utf-8")
|
|
391
|
-
result = (
|
|
392
|
-
struct.pack(b"<II", len(encoded), BMK_STRING | BMK_ST_ONE) + encoded
|
|
393
|
-
)
|
|
394
|
-
elif isinstance(item, bytes):
|
|
395
|
-
result = struct.pack(b"<II", len(item), BMK_STRING | BMK_ST_ONE) + item
|
|
396
|
-
elif isinstance(item, Data):
|
|
397
|
-
result = struct.pack(
|
|
398
|
-
b"<II", len(item.bytes), BMK_DATA | BMK_ST_ONE
|
|
399
|
-
) + bytes(item.bytes)
|
|
400
|
-
elif isinstance(item, bytearray):
|
|
401
|
-
result = struct.pack(b"<II", len(item), BMK_DATA | BMK_ST_ONE) + bytes(item)
|
|
402
|
-
elif isinstance(item, int):
|
|
403
|
-
if item > -0x80000000 and item < 0x7FFFFFFF:
|
|
404
|
-
result = struct.pack(b"<IIi", 4, BMK_NUMBER | kCFNumberSInt32Type, item)
|
|
405
|
-
else:
|
|
406
|
-
result = struct.pack(b"<IIq", 8, BMK_NUMBER | kCFNumberSInt64Type, item)
|
|
407
|
-
elif isinstance(item, float):
|
|
408
|
-
result = struct.pack(b"<IId", 8, BMK_NUMBER | kCFNumberFloat64Type, item)
|
|
409
|
-
elif isinstance(item, datetime.datetime):
|
|
410
|
-
secs = item - osx_epoch
|
|
411
|
-
result = struct.pack(b"<II", 8, BMK_DATE | BMK_ST_ZERO) + struct.pack(
|
|
412
|
-
b">d", float(secs.total_seconds())
|
|
413
|
-
)
|
|
414
|
-
|
|
415
|
-
elif isinstance(item, uuid.UUID):
|
|
416
|
-
result = struct.pack(b"<II", 16, BMK_UUID | BMK_ST_ONE) + item.bytes
|
|
417
|
-
elif isinstance(item, URL):
|
|
418
|
-
if item.base:
|
|
419
|
-
baseoff = offset + 16
|
|
420
|
-
reloff, baseenc = cls._encode_item(item.base, baseoff)
|
|
421
|
-
xoffset, relenc = cls._encode_item(item.relative, reloff)
|
|
422
|
-
result = b"".join(
|
|
423
|
-
[
|
|
424
|
-
struct.pack(
|
|
425
|
-
b"<IIII", 8, BMK_URL | BMK_URL_ST_RELATIVE, baseoff, reloff
|
|
426
|
-
),
|
|
427
|
-
baseenc,
|
|
428
|
-
relenc,
|
|
429
|
-
]
|
|
430
|
-
)
|
|
431
|
-
else:
|
|
432
|
-
encoded = item.relative.encode("utf-8")
|
|
433
|
-
result = (
|
|
434
|
-
struct.pack(b"<II", len(encoded), BMK_URL | BMK_URL_ST_ABSOLUTE)
|
|
435
|
-
+ encoded
|
|
436
|
-
)
|
|
437
|
-
elif isinstance(item, list):
|
|
438
|
-
ioffset = offset + 8 + len(item) * 4
|
|
439
|
-
result = [struct.pack(b"<II", len(item) * 4, BMK_ARRAY | BMK_ST_ONE)]
|
|
440
|
-
enc = []
|
|
441
|
-
for elt in item:
|
|
442
|
-
result.append(struct.pack(b"<I", ioffset))
|
|
443
|
-
ioffset, ienc = cls._encode_item(elt, ioffset)
|
|
444
|
-
enc.append(ienc)
|
|
445
|
-
result = b"".join(result + enc)
|
|
446
|
-
elif isinstance(item, dict):
|
|
447
|
-
ioffset = offset + 8 + len(item) * 8
|
|
448
|
-
result = [struct.pack(b"<II", len(item) * 8, BMK_DICT | BMK_ST_ONE)]
|
|
449
|
-
enc = []
|
|
450
|
-
for k, v in item.items():
|
|
451
|
-
result.append(struct.pack(b"<I", ioffset))
|
|
452
|
-
ioffset, ienc = cls._encode_item(k, ioffset)
|
|
453
|
-
enc.append(ienc)
|
|
454
|
-
result.append(struct.pack(b"<I", ioffset))
|
|
455
|
-
ioffset, ienc = cls._encode_item(v, ioffset)
|
|
456
|
-
enc.append(ienc)
|
|
457
|
-
result = b"".join(result + enc)
|
|
458
|
-
elif item is None:
|
|
459
|
-
result = struct.pack(b"<II", 0, BMK_NULL | BMK_ST_ONE)
|
|
460
|
-
else:
|
|
461
|
-
raise ValueError("Unknown item type when encoding: %s" % item)
|
|
462
|
-
|
|
463
|
-
offset += len(result)
|
|
464
|
-
|
|
465
|
-
# Pad to a multiple of 4 bytes
|
|
466
|
-
if offset & 3:
|
|
467
|
-
extra = 4 - (offset & 3)
|
|
468
|
-
result += b"\0" * extra
|
|
469
|
-
offset += extra
|
|
470
|
-
|
|
471
|
-
return (offset, result)
|
|
472
|
-
|
|
473
|
-
def to_bytes(self):
|
|
474
|
-
"""Convert this :class:`Bookmark` to a byte representation."""
|
|
475
|
-
|
|
476
|
-
result = []
|
|
477
|
-
tocs = []
|
|
478
|
-
offset = 4 # For the offset to the first TOC
|
|
479
|
-
|
|
480
|
-
# Generate the data and build the TOCs
|
|
481
|
-
for tid, toc in self.tocs:
|
|
482
|
-
entries = []
|
|
483
|
-
|
|
484
|
-
for k, v in toc.items():
|
|
485
|
-
if isinstance(k, str):
|
|
486
|
-
noffset = offset
|
|
487
|
-
voffset, enc = self._encode_item(k, offset)
|
|
488
|
-
result.append(enc)
|
|
489
|
-
offset, enc = self._encode_item(v, voffset)
|
|
490
|
-
result.append(enc)
|
|
491
|
-
entries.append((noffset | 0x80000000, voffset))
|
|
492
|
-
else:
|
|
493
|
-
entries.append((k, offset))
|
|
494
|
-
offset, enc = self._encode_item(v, offset)
|
|
495
|
-
result.append(enc)
|
|
496
|
-
|
|
497
|
-
# TOC entries must be sorted - CoreServicesInternal does a
|
|
498
|
-
# binary search to find data
|
|
499
|
-
entries.sort()
|
|
500
|
-
|
|
501
|
-
tocs.append(
|
|
502
|
-
(tid, b"".join([struct.pack(b"<III", k, o, 0) for k, o in entries]))
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
first_toc_offset = offset
|
|
506
|
-
|
|
507
|
-
# Now generate the TOC headers
|
|
508
|
-
for ndx, toc in enumerate(tocs):
|
|
509
|
-
tid, data = toc
|
|
510
|
-
if ndx == len(tocs) - 1:
|
|
511
|
-
next_offset = 0
|
|
512
|
-
else:
|
|
513
|
-
next_offset = offset + 20 + len(data)
|
|
514
|
-
|
|
515
|
-
result.append(
|
|
516
|
-
struct.pack(
|
|
517
|
-
b"<IIIII",
|
|
518
|
-
len(data) - 8,
|
|
519
|
-
0xFFFFFFFE,
|
|
520
|
-
tid,
|
|
521
|
-
next_offset,
|
|
522
|
-
len(data) // 12,
|
|
523
|
-
)
|
|
524
|
-
)
|
|
525
|
-
result.append(data)
|
|
526
|
-
|
|
527
|
-
offset += 20 + len(data)
|
|
528
|
-
|
|
529
|
-
# Finally, add the header (and the first TOC offset, which isn't part
|
|
530
|
-
# of the header, but goes just after it)
|
|
531
|
-
header = struct.pack(
|
|
532
|
-
b"<4sIIIQQQQI",
|
|
533
|
-
b"book",
|
|
534
|
-
offset + 48,
|
|
535
|
-
0x10040000,
|
|
536
|
-
48,
|
|
537
|
-
0,
|
|
538
|
-
0,
|
|
539
|
-
0,
|
|
540
|
-
0,
|
|
541
|
-
first_toc_offset,
|
|
542
|
-
)
|
|
543
|
-
|
|
544
|
-
result.insert(0, header)
|
|
545
|
-
|
|
546
|
-
return b"".join(result)
|
|
547
|
-
|
|
548
|
-
@classmethod
|
|
549
|
-
def for_file(cls, path):
|
|
550
|
-
"""Construct a :class:`Bookmark` for a given file."""
|
|
551
|
-
|
|
552
|
-
# Find the filesystem
|
|
553
|
-
st = osx.statfs(path)
|
|
554
|
-
vol_path = st.f_mntonname.decode("utf-8")
|
|
555
|
-
|
|
556
|
-
# Grab its attributes
|
|
557
|
-
attrs = [
|
|
558
|
-
osx.ATTR_CMN_CRTIME,
|
|
559
|
-
osx.ATTR_VOL_SIZE | osx.ATTR_VOL_NAME | osx.ATTR_VOL_UUID,
|
|
560
|
-
0,
|
|
561
|
-
0,
|
|
562
|
-
0,
|
|
563
|
-
]
|
|
564
|
-
volinfo = osx.getattrlist(vol_path, attrs, 0)
|
|
565
|
-
|
|
566
|
-
vol_crtime = volinfo[0]
|
|
567
|
-
vol_size = volinfo[1]
|
|
568
|
-
vol_name = volinfo[2]
|
|
569
|
-
vol_uuid = volinfo[3]
|
|
570
|
-
|
|
571
|
-
# Also grab various attributes of the file
|
|
572
|
-
attrs = [
|
|
573
|
-
(osx.ATTR_CMN_OBJTYPE | osx.ATTR_CMN_CRTIME | osx.ATTR_CMN_FILEID),
|
|
574
|
-
0,
|
|
575
|
-
0,
|
|
576
|
-
0,
|
|
577
|
-
0,
|
|
578
|
-
]
|
|
579
|
-
info = osx.getattrlist(path, attrs, osx.FSOPT_NOFOLLOW)
|
|
580
|
-
|
|
581
|
-
cnid = info[2]
|
|
582
|
-
crtime = info[1]
|
|
583
|
-
|
|
584
|
-
if info[0] == osx.VREG:
|
|
585
|
-
flags = kCFURLResourceIsRegularFile
|
|
586
|
-
elif info[0] == osx.VDIR:
|
|
587
|
-
flags = kCFURLResourceIsDirectory
|
|
588
|
-
elif info[0] == osx.VLNK:
|
|
589
|
-
flags = kCFURLResourceIsSymbolicLink
|
|
590
|
-
else:
|
|
591
|
-
flags = kCFURLResourceIsRegularFile
|
|
592
|
-
|
|
593
|
-
dirname, filename = os.path.split(path)
|
|
594
|
-
|
|
595
|
-
relcount = 0
|
|
596
|
-
if not os.path.isabs(dirname):
|
|
597
|
-
curdir = os.getcwd()
|
|
598
|
-
head, tail = os.path.split(curdir)
|
|
599
|
-
relcount = 0
|
|
600
|
-
while head and tail:
|
|
601
|
-
relcount += 1
|
|
602
|
-
head, tail = os.path.split(head)
|
|
603
|
-
dirname = os.path.join(curdir, dirname)
|
|
604
|
-
|
|
605
|
-
# ?? foldername = os.path.basename(dirname)
|
|
606
|
-
|
|
607
|
-
rel_path = os.path.relpath(path, vol_path)
|
|
608
|
-
|
|
609
|
-
# Build the path arrays
|
|
610
|
-
name_path = []
|
|
611
|
-
cnid_path = []
|
|
612
|
-
head, tail = os.path.split(rel_path)
|
|
613
|
-
if not tail:
|
|
614
|
-
head, tail = os.path.split(head)
|
|
615
|
-
while head or tail:
|
|
616
|
-
if head:
|
|
617
|
-
attrs = [osx.ATTR_CMN_FILEID, 0, 0, 0, 0]
|
|
618
|
-
info = osx.getattrlist(os.path.join(vol_path, head), attrs, 0)
|
|
619
|
-
cnid_path.insert(0, info[0])
|
|
620
|
-
head, tail = os.path.split(head)
|
|
621
|
-
name_path.insert(0, tail)
|
|
622
|
-
else:
|
|
623
|
-
head, tail = os.path.split(head)
|
|
624
|
-
name_path.append(filename)
|
|
625
|
-
cnid_path.append(cnid)
|
|
626
|
-
|
|
627
|
-
url_lengths = [relcount, len(name_path) - relcount]
|
|
628
|
-
|
|
629
|
-
fileprops = Data(struct.pack(b"<QQQ", flags, 0x0F, 0))
|
|
630
|
-
volprops = Data(
|
|
631
|
-
struct.pack(
|
|
632
|
-
b"<QQQ",
|
|
633
|
-
0x81 | kCFURLVolumeSupportsPersistentIDs,
|
|
634
|
-
0x13EF | kCFURLVolumeSupportsPersistentIDs,
|
|
635
|
-
0,
|
|
636
|
-
)
|
|
637
|
-
)
|
|
638
|
-
|
|
639
|
-
toc = {
|
|
640
|
-
kBookmarkPath: name_path,
|
|
641
|
-
kBookmarkCNIDPath: cnid_path,
|
|
642
|
-
kBookmarkFileCreationDate: crtime,
|
|
643
|
-
kBookmarkFileProperties: fileprops,
|
|
644
|
-
kBookmarkContainingFolder: len(name_path) - 2,
|
|
645
|
-
kBookmarkVolumePath: vol_path,
|
|
646
|
-
kBookmarkVolumeIsRoot: vol_path == "/",
|
|
647
|
-
kBookmarkVolumeURL: URL("file://" + vol_path),
|
|
648
|
-
kBookmarkVolumeName: vol_name,
|
|
649
|
-
kBookmarkVolumeSize: vol_size,
|
|
650
|
-
kBookmarkVolumeCreationDate: vol_crtime,
|
|
651
|
-
kBookmarkVolumeUUID: str(vol_uuid).upper(),
|
|
652
|
-
kBookmarkVolumeProperties: volprops,
|
|
653
|
-
kBookmarkCreationOptions: 512,
|
|
654
|
-
kBookmarkWasFileReference: True,
|
|
655
|
-
kBookmarkUserName: "unknown",
|
|
656
|
-
kBookmarkUID: 99,
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
if relcount:
|
|
660
|
-
toc[kBookmarkURLLengths] = url_lengths
|
|
661
|
-
|
|
662
|
-
return Bookmark([(1, toc)])
|
|
663
|
-
|
|
664
|
-
def __repr__(self):
|
|
665
|
-
result = ["Bookmark(["]
|
|
666
|
-
for tid, toc in self.tocs:
|
|
667
|
-
result.append("(0x%x, {\n" % tid)
|
|
668
|
-
for k, v in toc.items():
|
|
669
|
-
if isinstance(k, str):
|
|
670
|
-
kf = repr(k)
|
|
671
|
-
else:
|
|
672
|
-
kf = "0x%04x" % k
|
|
673
|
-
result.append(f" {kf}: {v!r}\n")
|
|
674
|
-
result.append("}),\n")
|
|
675
|
-
result.append("])")
|
|
676
|
-
|
|
677
|
-
return "".join(result)
|