Skip to content

Latest commit

 

History

15 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

apple-binary-plist

CI

A minimal-dependency Common Lisp parser for Apple binary property lists (the bplist00 format produced by CFBinaryPlist and plutil -convert binary1). Layered on top of the core parser are an NSKeyedArchiver unarchiver, a Safari .webarchive resource extractor, a human-readable structure dumper, and a standalone bplist-dump command-line tool.

The parser is written to survive hostile input: every length/count field is bounds-checked before allocation, nesting depth and object/byte budgets are capped, reference cycles are detected, and only a single plist-error condition type ever escapes the public entry points.

Dependencies are kept to a minimum: babel for portable UTF-8/UTF-16 string decoding, and ieee-floats for a float-decode fallback used only on non-SBCL implementations (on SBCL it is never called).

Installation

Dependencies are managed with ocicl:

ocicl install

Then load the system:

(asdf:load-system :apple-binary-plist)   ; nickname: abplist

Usage

Parsing

(abplist:parse-file #p"settings.plist")   ; => the top-level Lisp value
(abplist:parse bytes)                     ; from an (unsigned-byte 8) vector
(abplist:parse-stream stream)             ; from a byte stream

plist types map onto Lisp values as follows:

plist type Lisp representation
null/true/false +null+ / +true+ / +false+ (keywords)
integer integer
real double-float / single-float
date plist-date struct (Cocoa 2001 epoch)
data (simple-array (unsigned-byte 8) (*))
string string
uid plist-uid struct
array simple-vector
set list
dictionary hash-table (:test #'equal)

Use plist-date-universal-time to convert a plist-date to Common Lisp universal time.

Dumping structure

dump/dump-file print an indented, type-annotated tree, eliding long strings and data blobs so the output stays scannable. Here is a real dump of the sample .webarchive committed under test/ (a saved copy of this project's own GitHub page), trimmed to its first subresource:

(abplist:dump-file #p"test/example.webarchive")
dict (2 entries)
  "WebMainResource" => dict (5 entries)
    "WebResourceData" => data[362290] 3C 21 44 4F 43 54 59 50 45 20 68 74 6D 6C 3E 3C ...
    "WebResourceFrameName" => string[0] ""
    "WebResourceMIMEType" => string[9] "text/html"
    "WebResourceTextEncodingName" => string[5] "UTF-8"
    "WebResourceURL" => string[45] "https://github.com/lispnik/apple-binary-plist"
  "WebSubresources" => array (128 elements)
    [0] dict (4 entries)
      "WebResourceData" => data[76753] 5B 64 61 74 61 2D 63 6F 6C 6F 72 2D 6D 6F 64 65 ...
      "WebResourceMIMEType" => string[8] "text/css"
      "WebResourceResponse" => data[2251] 62 70 6C 69 73 74 30 30 D4 01 02 03 04 05 06 07 ...
      "WebResourceURL" => string[65] "https://github.githubassets.com/assets/light-62b06818b06b09b..."
    ... 127 more subresources ...

Note that each WebResourceResponse blob is itself a binary plist — its data begins with 62 70 6C 69 73 74 30 30, i.e. bplist00. It is an NSKeyedArchiver-encoded NSHTTPURLResponse you can decode with unarchive.

NSKeyedArchiver

unarchive walks an archive's flat $objects table, follows the UID references, and rebuilds the natural nested object graph. Foundation container and value classes (NSDictionary, NSArray, NSString, NSDate, NSURL, NSUUID, ...) are decoded to native values; unknown classes are preserved as archived-object structs.

(abplist:unarchive-file #p"archive.plist")
(abplist:unarchive bytes :root "someTopKey")
(abplist:keyed-archive-p (abplist:parse bytes))   ; recogniser

Extracting a .webarchive

(abplist:extract-webarchive #p"page.webarchive" #p"out/")
;; => list of extracted-resource structs; each WebResourceData blob is written
;;    into out/ under a reconstructed host/path tree.

By default a write that would escape the output directory (e.g. via a planted symlink) is refused. See the docstring for :flatten, :max-files, :if-exists, and :follow-symlinks.

Command-line tool

Build the standalone executable with the Makefile (which calls ASDF's program-op):

make            # or: sbcl --non-interactive --eval '(asdf:make :apple-binary-plist/cli)'
# => bin/bplist-dump
Usage: bplist-dump [options] FILE

  -u, --unarchive       decode an NSKeyedArchiver graph before dumping
  -s, --max-string N    truncate strings to N characters (default 60)
  -b, --max-bytes N     truncate data blobs to N bytes (default 16)
  -x, --extract DIR     extract .webarchive WebResourceData blobs into DIR
      --flatten         with --extract, write all files directly into DIR
      --no-overwrite    with --extract, refuse to overwrite existing files
      --max-files N     with --extract, refuse archives with over N resources
  -h, --help            show this help and exit
  -v, --version         show version and exit

With no FILE, or FILE of "-", the plist is read from standard input.

Running the tests

sbcl --non-interactive --eval '(asdf:test-system :apple-binary-plist)'

Implementation support

The core library runs on any Common Lisp with full (21-bit) Unicode characters — SBCL, CCL, ECL, ABCL, Clasp, LispWorks. String decoding goes through babel and float decoding through ieee-floats (SBCL uses a faster native sb-kernel path), so no implementation-specific code is required to parse. CLISP works only if built with wide characters, since UTF-16 and astral code points need them. CI runs the full suite on SBCL, CCL, and ECL across Linux and macOS (CCL on Linux only — no CCL binary exists for arm64 macOS).

The bplist-dump CLI reads standard input through a fast sb-sys path on SBCL and through /dev/stdin on other implementations, so piping works across Lisps on macOS and Linux. There is no Windows stdin path — pass a FILE argument there instead of piping.

License

MIT. See LICENSE.

About

A minimal-dependency Common Lisp parser for Apple binary property lists (bplist00), with NSKeyedArchiver unarchiving and .webarchive extraction.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages