Forráskód Böngészése

Initial commit and macOS install

Filippo Valsorda 6 éve
commit
cbca29dc62
28 módosított fájl, 3620 hozzáadás és 0 törlés
  1. 15 0
      Gopkg.lock
  2. 34 0
      Gopkg.toml
  3. 222 0
      main.go
  4. 98 0
      truststore_darwin.go
  5. 39 0
      vendor/github.com/DHowett/go-plist/.gitlab-ci.yml
  6. 58 0
      vendor/github.com/DHowett/go-plist/LICENSE
  7. 21 0
      vendor/github.com/DHowett/go-plist/README.md
  8. 26 0
      vendor/github.com/DHowett/go-plist/bplist.go
  9. 303 0
      vendor/github.com/DHowett/go-plist/bplist_generator.go
  10. 353 0
      vendor/github.com/DHowett/go-plist/bplist_parser.go
  11. 119 0
      vendor/github.com/DHowett/go-plist/decode.go
  12. 5 0
      vendor/github.com/DHowett/go-plist/doc.go
  13. 126 0
      vendor/github.com/DHowett/go-plist/encode.go
  14. 17 0
      vendor/github.com/DHowett/go-plist/fuzz.go
  15. 186 0
      vendor/github.com/DHowett/go-plist/marshal.go
  16. 50 0
      vendor/github.com/DHowett/go-plist/must.go
  17. 85 0
      vendor/github.com/DHowett/go-plist/plist.go
  18. 139 0
      vendor/github.com/DHowett/go-plist/plist_types.go
  19. 226 0
      vendor/github.com/DHowett/go-plist/text_generator.go
  20. 515 0
      vendor/github.com/DHowett/go-plist/text_parser.go
  21. 43 0
      vendor/github.com/DHowett/go-plist/text_tables.go
  22. 170 0
      vendor/github.com/DHowett/go-plist/typeinfo.go
  23. 317 0
      vendor/github.com/DHowett/go-plist/unmarshal.go
  24. 25 0
      vendor/github.com/DHowett/go-plist/util.go
  25. 185 0
      vendor/github.com/DHowett/go-plist/xml_generator.go
  26. 216 0
      vendor/github.com/DHowett/go-plist/xml_parser.go
  27. 20 0
      vendor/github.com/DHowett/go-plist/zerocopy.go
  28. 7 0
      vendor/github.com/DHowett/go-plist/zerocopy_appengine.go

+ 15 - 0
Gopkg.lock

@@ -0,0 +1,15 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+  branch = "master"
+  name = "github.com/DHowett/go-plist"
+  packages = ["."]
+  revision = "500bd5b9081b5957ac10389f86e069869f00c348"
+
+[solve-meta]
+  analyzer-name = "dep"
+  analyzer-version = 1
+  inputs-digest = "b3ca26cded7865674be15028cfaf931f68a028db1f3c1588b9d0ae96e41cfbd8"
+  solver-name = "gps-cdcl"
+  solver-version = 1

+ 34 - 0
Gopkg.toml

@@ -0,0 +1,34 @@
+# Gopkg.toml example
+#
+# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+#   name = "github.com/user/project"
+#   version = "1.0.0"
+#
+# [[constraint]]
+#   name = "github.com/user/project2"
+#   branch = "dev"
+#   source = "github.com/myfork/project2"
+#
+# [[override]]
+#   name = "github.com/x/y"
+#   version = "2.4.0"
+#
+# [prune]
+#   non-go = false
+#   go-tests = true
+#   unused-packages = true
+
+
+[prune]
+  go-tests = true
+  unused-packages = true
+
+[[constraint]]
+  branch = "master"
+  name = "github.com/DHowett/go-plist"

+ 222 - 0
main.go

@@ -0,0 +1,222 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Command mkcert is a simple zero-config tool to make development certificates.
+package main
+
+import (
+	"crypto"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"flag"
+	"io/ioutil"
+	"log"
+	"math/big"
+	"os"
+	"path/filepath"
+	"runtime"
+	"time"
+)
+
+var installFlag = flag.Bool("install", false, "install the local root CA in the system trust store")
+
+func main() {
+	log.SetFlags(0)
+	flag.Parse()
+	(&mkcert{}).Run()
+}
+
+const rootName = "rootCA.pem"
+const keyName = "rootCA-key.pem"
+
+var rootSubject = pkix.Name{
+	Organization: []string{"mkcert development CA"},
+}
+
+type mkcert struct {
+	CAROOT string
+	caCert *x509.Certificate
+	caKey  crypto.PrivateKey
+
+	// The system cert pool is only loaded once. After installing the root, checks
+	// will keep failing until the next execution. TODO: maybe execve?
+	// https://github.com/golang/go/issues/24540 (thanks, myself)
+	ignoreCheckFailure bool
+}
+
+func (m *mkcert) Run() {
+	m.CAROOT = getCAROOT()
+	if m.CAROOT == "" {
+		log.Fatalln("ERROR: failed to find the default CA location, set one as the CAROOT env var")
+	}
+	fatalIfErr(os.MkdirAll(m.CAROOT, 0755), "failed to create the CAROOT")
+	m.loadCA()
+	if *installFlag {
+		m.install()
+	} else if !m.check() {
+		log.Println("Warning: the local CA is not installed in the system trust store! ⚠️")
+		log.Println("Run \"mkcert -install\" to avoid verification errors ‼️")
+	}
+}
+
+// loadCA will load or create the CA at CAROOT.
+func (m *mkcert) loadCA() {
+	if _, err := os.Stat(filepath.Join(m.CAROOT, rootName)); os.IsNotExist(err) {
+		m.newCA()
+	} else {
+		log.Printf("Using the local CA at \"%s\" ✨\n", m.CAROOT)
+	}
+
+	certPEMBlock, err := ioutil.ReadFile(filepath.Join(m.CAROOT, rootName))
+	fatalIfErr(err, "failed to read the CA certificate")
+	keyPEMBlock, err := ioutil.ReadFile(filepath.Join(m.CAROOT, keyName))
+	fatalIfErr(err, "failed to read the CA key")
+
+	certDERBlock, _ := pem.Decode(certPEMBlock)
+	if certDERBlock == nil || certDERBlock.Type != "CERTIFICATE" {
+		log.Fatalln("ERROR: failed to read the CA certificate: unexpected content")
+	}
+	m.caCert, err = x509.ParseCertificate(certDERBlock.Bytes)
+	fatalIfErr(err, "failed to parse the CA certificate")
+
+	keyDERBlock, _ := pem.Decode(keyPEMBlock)
+	if keyDERBlock == nil || keyDERBlock.Type != "PRIVATE KEY" {
+		log.Fatalln("ERROR: failed to read the CA key: unexpected content")
+	}
+	m.caKey, err = x509.ParsePKCS8PrivateKey(keyDERBlock.Bytes)
+	fatalIfErr(err, "failed to parse the CA key")
+}
+
+func (m *mkcert) newCA() {
+	priv, err := rsa.GenerateKey(rand.Reader, 3072)
+	fatalIfErr(err, "failed to generate the CA key")
+
+	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
+	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+	fatalIfErr(err, "failed to generate serial number")
+
+	tpl := &x509.Certificate{
+		SerialNumber: serialNumber,
+		Subject:      rootSubject,
+
+		NotAfter:  time.Now().AddDate(10, 0, 0),
+		NotBefore: time.Now().AddDate(0, 0, -1),
+
+		KeyUsage: x509.KeyUsageCertSign,
+
+		BasicConstraintsValid: true,
+		IsCA: true,
+	}
+
+	pub := priv.PublicKey
+	cert, err := x509.CreateCertificate(rand.Reader, tpl, tpl, &pub, priv)
+	fatalIfErr(err, "failed to generate CA certificate")
+
+	privDER, err := x509.MarshalPKCS8PrivateKey(priv)
+	fatalIfErr(err, "failed to encode CA key")
+	err = ioutil.WriteFile(filepath.Join(m.CAROOT, keyName), pem.EncodeToMemory(
+		&pem.Block{Type: "PRIVATE KEY", Bytes: privDER}), 0400)
+	fatalIfErr(err, "failed to save CA key")
+
+	err = ioutil.WriteFile(filepath.Join(m.CAROOT, rootName), pem.EncodeToMemory(
+		&pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0400)
+	fatalIfErr(err, "failed to save CA key")
+
+	log.Printf("Created a new local CA at \"%s\" 💥\n", m.CAROOT)
+}
+
+func getCAROOT() string {
+	if env := os.Getenv("CAROOT"); env != "" {
+		return env
+	}
+
+	var dir string
+	switch runtime.GOOS {
+	case "windows":
+		dir = os.Getenv("LocalAppData")
+	case "darwin":
+		dir = os.Getenv("HOME")
+		if dir == "" {
+			return ""
+		}
+		dir = filepath.Join(dir, "Library", "Application Support")
+	default: // Unix
+		dir = os.Getenv("XDG_DATA_HOME")
+		if dir == "" {
+			dir = os.Getenv("HOME")
+			if dir == "" {
+				return ""
+			}
+			dir = filepath.Join(dir, ".local", "share")
+		}
+	}
+	return filepath.Join(dir, "mkcert")
+}
+
+func (m *mkcert) install() {
+	if m.check() {
+		return
+	}
+
+	m.installPlatform()
+	m.ignoreCheckFailure = true
+
+	/*
+		switch runtime.GOOS {
+		case "darwin":
+			m.installDarwin()
+		default:
+			log.Println("Installing is not available on your platform 👎")
+			log.Fatalf("If you know how, you can install the certificate at \"%s\" in your system trust store", filepath.Join(m.CAROOT, rootName))
+		}
+	*/
+
+	if m.check() { // useless, see comment on ignoreCheckFailure
+		log.Println("The local CA is now installed in the system trust store! ⚡️")
+	} else {
+		log.Fatalln("Installing failed. Please report the issue with details about your environment at https://github.com/FiloSottile/mkcert/issues/new 👎")
+	}
+}
+
+func (m *mkcert) check() bool {
+	if m.ignoreCheckFailure {
+		return true
+	}
+
+	/*
+		priv, err := rsa.GenerateKey(rand.Reader, 2048)
+		fatalIfErr(err, "failed to generate the test key")
+
+		tpl := &x509.Certificate{
+			SerialNumber: big.NewInt(42),
+			DNSNames:     []string{"test.mkcert.invalid"},
+
+			NotAfter:  time.Now().AddDate(0, 0, 1),
+			NotBefore: time.Now().AddDate(0, 0, -1),
+
+			KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
+			ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+			BasicConstraintsValid: true,
+		}
+
+		pub := priv.PublicKey
+		cert, err := x509.CreateCertificate(rand.Reader, tpl, m.caCert, &pub, m.caKey)
+		fatalIfErr(err, "failed to generate test certificate")
+
+		c, err := x509.ParseCertificate(cert)
+		fatalIfErr(err, "failed to parse test certificate")
+	*/
+
+	_, err := m.caCert.Verify(x509.VerifyOptions{})
+	return err == nil
+}
+
+func fatalIfErr(err error, msg string) {
+	if err != nil {
+		log.Fatalf("ERROR: %s: %s", msg, err)
+	}
+}

+ 98 - 0
truststore_darwin.go

@@ -0,0 +1,98 @@
+package main
+
+import (
+	"bytes"
+	"encoding/asn1"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+
+	"github.com/DHowett/go-plist"
+)
+
+// https://github.com/golang/go/issues/24652#issuecomment-399826583
+var trustSettings []interface{}
+var _, _ = plist.Unmarshal(trustSettingsData, &trustSettings)
+var trustSettingsData = []byte(`
+<array>
+	<dict>
+		<key>kSecTrustSettingsPolicy</key>
+		<data>
+		KoZIhvdjZAED
+		</data>
+		<key>kSecTrustSettingsPolicyName</key>
+		<string>sslServer</string>
+		<key>kSecTrustSettingsResult</key>
+		<integer>1</integer>
+	</dict>
+	<dict>
+		<key>kSecTrustSettingsPolicy</key>
+		<data>
+		KoZIhvdjZAEC
+		</data>
+		<key>kSecTrustSettingsPolicyName</key>
+		<string>basicX509</string>
+		<key>kSecTrustSettingsResult</key>
+		<integer>1</integer>
+	</dict>
+</array>
+`)
+
+func (m *mkcert) installPlatform() {
+	cmd := exec.Command("sudo", "security", "add-trusted-cert", "-d", "-k", "/Library/Keychains/System.keychain", filepath.Join(m.CAROOT, rootName))
+	out, err := cmd.CombinedOutput()
+	fatalIfCmdErr(err, "security add-trusted-cert", out)
+
+	// Make trustSettings explicit, as older Go does not know the defaults.
+	// https://github.com/golang/go/issues/24652
+
+	plistFile, err := ioutil.TempFile("", "trust-settings")
+	fatalIfErr(err, "failed to create temp file")
+	defer os.Remove(plistFile.Name())
+
+	cmd = exec.Command("sudo", "security", "trust-settings-export", "-d", plistFile.Name())
+	out, err = cmd.CombinedOutput()
+	fatalIfCmdErr(err, "security trust-settings-export", out)
+
+	plistData, err := ioutil.ReadFile(plistFile.Name())
+	fatalIfErr(err, "failed to read trust settings")
+	var plistRoot map[string]interface{}
+	_, err = plist.Unmarshal(plistData, &plistRoot)
+	fatalIfErr(err, "failed to parse trust settings")
+
+	rootSubjectASN1, _ := asn1.Marshal(rootSubject.ToRDNSequence())
+
+	if plistRoot["trustVersion"].(uint64) != 1 {
+		log.Fatalln("ERROR: unsupported trust settings version:", plistRoot["trustVersion"])
+	}
+	trustList := plistRoot["trustList"].(map[string]interface{})
+	for key := range trustList {
+		entry := trustList[key].(map[string]interface{})
+		if _, ok := entry["issuerName"]; !ok {
+			continue
+		}
+		issuerName := entry["issuerName"].([]byte)
+		if !bytes.Equal(rootSubjectASN1, issuerName) {
+			continue
+		}
+		entry["trustSettings"] = trustSettings
+		break
+	}
+
+	plistData, err = plist.MarshalIndent(plistRoot, plist.XMLFormat, "\t")
+	fatalIfErr(err, "failed to serialize trust settings")
+	err = ioutil.WriteFile(plistFile.Name(), plistData, 0600)
+	fatalIfErr(err, "failed to write trust settings")
+
+	cmd = exec.Command("sudo", "security", "trust-settings-import", "-d", plistFile.Name())
+	out, err = cmd.CombinedOutput()
+	fatalIfCmdErr(err, "security trust-settings-import", out)
+}
+
+func fatalIfCmdErr(err error, cmd string, out []byte) {
+	if err != nil {
+		log.Fatalf("ERROR: failed to execute \"%s\": %s\n\n%s\n", cmd, err, out)
+	}
+}

+ 39 - 0
vendor/github.com/DHowett/go-plist/.gitlab-ci.yml

@@ -0,0 +1,39 @@
+image: golang:alpine
+stages:
+    - test
+
+variables:
+    GO_PACKAGE: "howett.net/plist"
+
+before_script:
+    - "mkdir -p $(dirname $GOPATH/src/$GO_PACKAGE)"
+    - "ln -s $(pwd) $GOPATH/src/$GO_PACKAGE"
+    - "cd $GOPATH/src/$GO_PACKAGE"
+
+.template:go-test: &template-go-test
+    stage: test
+    script:
+        - go test
+
+go-test-cover:latest:
+    stage: test
+    script:
+        - go test -v -cover
+    coverage: '/^coverage: \d+\.\d+/'
+
+go-test-appengine:latest:
+    stage: test
+    script:
+        - go test -tags appengine
+
+go-test:1.6:
+    <<: *template-go-test
+    image: golang:1.6-alpine
+
+go-test:1.4:
+    <<: *template-go-test
+    image: golang:1.4-alpine
+
+go-test:1.2:
+    <<: *template-go-test
+    image: golang:1.2

+ 58 - 0
vendor/github.com/DHowett/go-plist/LICENSE

@@ -0,0 +1,58 @@
+Copyright (c) 2013, Dustin L. Howett. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met: 
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer. 
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution. 
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those
+of the authors and should not be interpreted as representing official policies, 
+either expressed or implied, of the FreeBSD Project.
+
+--------------------------------------------------------------------------------
+Parts of this package were made available under the license covering
+the Go language and all attended core libraries. That license follows.
+--------------------------------------------------------------------------------
+
+Copyright (c) 2012 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 21 - 0
vendor/github.com/DHowett/go-plist/README.md

@@ -0,0 +1,21 @@
+# plist - A pure Go property list transcoder [![coverage report](https://gitlab.howett.net/go/plist/badges/master/coverage.svg)](https://gitlab.howett.net/go/plist/commits/master)
+## INSTALL
+```
+$ go get howett.net/plist
+```
+
+## FEATURES
+* Supports encoding/decoding property lists (Apple XML, Apple Binary, OpenStep and GNUStep) from/to arbitrary Go types
+
+## USE
+```go
+package main
+import (
+	"howett.net/plist"
+	"os"
+)
+func main() {
+	encoder := plist.NewEncoder(os.Stdout)
+	encoder.Encode(map[string]string{"hello": "world"})
+}
+```

+ 26 - 0
vendor/github.com/DHowett/go-plist/bplist.go

@@ -0,0 +1,26 @@
+package plist
+
+type bplistTrailer struct {
+	Unused            [5]uint8
+	SortVersion       uint8
+	OffsetIntSize     uint8
+	ObjectRefSize     uint8
+	NumObjects        uint64
+	TopObject         uint64
+	OffsetTableOffset uint64
+}
+
+const (
+	bpTagNull        uint8 = 0x00
+	bpTagBoolFalse         = 0x08
+	bpTagBoolTrue          = 0x09
+	bpTagInteger           = 0x10
+	bpTagReal              = 0x20
+	bpTagDate              = 0x30
+	bpTagData              = 0x40
+	bpTagASCIIString       = 0x50
+	bpTagUTF16String       = 0x60
+	bpTagUID               = 0x80
+	bpTagArray             = 0xA0
+	bpTagDictionary        = 0xD0
+)

+ 303 - 0
vendor/github.com/DHowett/go-plist/bplist_generator.go

@@ -0,0 +1,303 @@
+package plist
+
+import (
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"io"
+	"time"
+	"unicode/utf16"
+)
+
+func bplistMinimumIntSize(n uint64) int {
+	switch {
+	case n <= uint64(0xff):
+		return 1
+	case n <= uint64(0xffff):
+		return 2
+	case n <= uint64(0xffffffff):
+		return 4
+	default:
+		return 8
+	}
+}
+
+func bplistValueShouldUnique(pval cfValue) bool {
+	switch pval.(type) {
+	case cfString, *cfNumber, *cfReal, cfDate, cfData:
+		return true
+	}
+	return false
+}
+
+type bplistGenerator struct {
+	writer   *countedWriter
+	objmap   map[interface{}]uint64 // maps pValue.hash()es to object locations
+	objtable []cfValue
+	trailer  bplistTrailer
+}
+
+func (p *bplistGenerator) flattenPlistValue(pval cfValue) {
+	key := pval.hash()
+	if bplistValueShouldUnique(pval) {
+		if _, ok := p.objmap[key]; ok {
+			return
+		}
+	}
+
+	p.objmap[key] = uint64(len(p.objtable))
+	p.objtable = append(p.objtable, pval)
+
+	switch pval := pval.(type) {
+	case *cfDictionary:
+		pval.sort()
+		for _, k := range pval.keys {
+			p.flattenPlistValue(cfString(k))
+		}
+		for _, v := range pval.values {
+			p.flattenPlistValue(v)
+		}
+	case *cfArray:
+		for _, v := range pval.values {
+			p.flattenPlistValue(v)
+		}
+	}
+}
+
+func (p *bplistGenerator) indexForPlistValue(pval cfValue) (uint64, bool) {
+	v, ok := p.objmap[pval.hash()]
+	return v, ok
+}
+
+func (p *bplistGenerator) generateDocument(root cfValue) {
+	p.objtable = make([]cfValue, 0, 16)
+	p.objmap = make(map[interface{}]uint64)
+	p.flattenPlistValue(root)
+
+	p.trailer.NumObjects = uint64(len(p.objtable))
+	p.trailer.ObjectRefSize = uint8(bplistMinimumIntSize(p.trailer.NumObjects))
+
+	p.writer.Write([]byte("bplist00"))
+
+	offtable := make([]uint64, p.trailer.NumObjects)
+	for i, pval := range p.objtable {
+		offtable[i] = uint64(p.writer.BytesWritten())
+		p.writePlistValue(pval)
+	}
+
+	p.trailer.OffsetIntSize = uint8(bplistMinimumIntSize(uint64(p.writer.BytesWritten())))
+	p.trailer.TopObject = p.objmap[root.hash()]
+	p.trailer.OffsetTableOffset = uint64(p.writer.BytesWritten())
+
+	for _, offset := range offtable {
+		p.writeSizedInt(offset, int(p.trailer.OffsetIntSize))
+	}
+
+	binary.Write(p.writer, binary.BigEndian, p.trailer)
+}
+
+func (p *bplistGenerator) writePlistValue(pval cfValue) {
+	if pval == nil {
+		return
+	}
+
+	switch pval := pval.(type) {
+	case *cfDictionary:
+		p.writeDictionaryTag(pval)
+	case *cfArray:
+		p.writeArrayTag(pval.values)
+	case cfString:
+		p.writeStringTag(string(pval))
+	case *cfNumber:
+		p.writeIntTag(pval.signed, pval.value)
+	case *cfReal:
+		if pval.wide {
+			p.writeRealTag(pval.value, 64)
+		} else {
+			p.writeRealTag(pval.value, 32)
+		}
+	case cfBoolean:
+		p.writeBoolTag(bool(pval))
+	case cfData:
+		p.writeDataTag([]byte(pval))
+	case cfDate:
+		p.writeDateTag(time.Time(pval))
+	case cfUID:
+		p.writeUIDTag(UID(pval))
+	default:
+		panic(fmt.Errorf("unknown plist type %t", pval))
+	}
+}
+
+func (p *bplistGenerator) writeSizedInt(n uint64, nbytes int) {
+	var val interface{}
+	switch nbytes {
+	case 1:
+		val = uint8(n)
+	case 2:
+		val = uint16(n)
+	case 4:
+		val = uint32(n)
+	case 8:
+		val = n
+	default:
+		panic(errors.New("illegal integer size"))
+	}
+	binary.Write(p.writer, binary.BigEndian, val)
+}
+
+func (p *bplistGenerator) writeBoolTag(v bool) {
+	tag := uint8(bpTagBoolFalse)
+	if v {
+		tag = bpTagBoolTrue
+	}
+	binary.Write(p.writer, binary.BigEndian, tag)
+}
+
+func (p *bplistGenerator) writeIntTag(signed bool, n uint64) {
+	var tag uint8
+	var val interface{}
+	switch {
+	case n <= uint64(0xff):
+		val = uint8(n)
+		tag = bpTagInteger | 0x0
+	case n <= uint64(0xffff):
+		val = uint16(n)
+		tag = bpTagInteger | 0x1
+	case n <= uint64(0xffffffff):
+		val = uint32(n)
+		tag = bpTagInteger | 0x2
+	case n > uint64(0x7fffffffffffffff) && !signed:
+		// 64-bit values are always *signed* in format 00.
+		// Any unsigned value that doesn't intersect with the signed
+		// range must be sign-extended and stored as a SInt128
+		val = n
+		tag = bpTagInteger | 0x4
+	default:
+		val = n
+		tag = bpTagInteger | 0x3
+	}
+
+	binary.Write(p.writer, binary.BigEndian, tag)
+	if tag&0xF == 0x4 {
+		// SInt128; in the absence of true 128-bit integers in Go,
+		// we'll just fake the top half. We only got here because
+		// we had an unsigned 64-bit int that didn't fit,
+		// so sign extend it with zeroes.
+		binary.Write(p.writer, binary.BigEndian, uint64(0))
+	}
+	binary.Write(p.writer, binary.BigEndian, val)
+}
+
+func (p *bplistGenerator) writeUIDTag(u UID) {
+	nbytes := bplistMinimumIntSize(uint64(u))
+	tag := uint8(bpTagUID | (nbytes - 1))
+
+	binary.Write(p.writer, binary.BigEndian, tag)
+	p.writeSizedInt(uint64(u), nbytes)
+}
+
+func (p *bplistGenerator) writeRealTag(n float64, bits int) {
+	var tag uint8 = bpTagReal | 0x3
+	var val interface{} = n
+	if bits == 32 {
+		val = float32(n)
+		tag = bpTagReal | 0x2
+	}
+
+	binary.Write(p.writer, binary.BigEndian, tag)
+	binary.Write(p.writer, binary.BigEndian, val)
+}
+
+func (p *bplistGenerator) writeDateTag(t time.Time) {
+	tag := uint8(bpTagDate) | 0x3
+	val := float64(t.In(time.UTC).UnixNano()) / float64(time.Second)
+	val -= 978307200 // Adjust to Apple Epoch
+
+	binary.Write(p.writer, binary.BigEndian, tag)
+	binary.Write(p.writer, binary.BigEndian, val)
+}
+
+func (p *bplistGenerator) writeCountedTag(tag uint8, count uint64) {
+	marker := tag
+	if count >= 0xF {
+		marker |= 0xF
+	} else {
+		marker |= uint8(count)
+	}
+
+	binary.Write(p.writer, binary.BigEndian, marker)
+
+	if count >= 0xF {
+		p.writeIntTag(false, count)
+	}
+}
+
+func (p *bplistGenerator) writeDataTag(data []byte) {
+	p.writeCountedTag(bpTagData, uint64(len(data)))
+	binary.Write(p.writer, binary.BigEndian, data)
+}
+
+func (p *bplistGenerator) writeStringTag(str string) {
+	for _, r := range str {
+		if r > 0x7F {
+			utf16Runes := utf16.Encode([]rune(str))
+			p.writeCountedTag(bpTagUTF16String, uint64(len(utf16Runes)))
+			binary.Write(p.writer, binary.BigEndian, utf16Runes)
+			return
+		}
+	}
+
+	p.writeCountedTag(bpTagASCIIString, uint64(len(str)))
+	binary.Write(p.writer, binary.BigEndian, []byte(str))
+}
+
+func (p *bplistGenerator) writeDictionaryTag(dict *cfDictionary) {
+	// assumption: sorted already; flattenPlistValue did this.
+	cnt := len(dict.keys)
+	p.writeCountedTag(bpTagDictionary, uint64(cnt))
+	vals := make([]uint64, cnt*2)
+	for i, k := range dict.keys {
+		// invariant: keys have already been "uniqued" (as PStrings)
+		keyIdx, ok := p.objmap[cfString(k).hash()]
+		if !ok {
+			panic(errors.New("failed to find key " + k + " in object map during serialization"))
+		}
+		vals[i] = keyIdx
+	}
+
+	for i, v := range dict.values {
+		// invariant: values have already been "uniqued"
+		objIdx, ok := p.indexForPlistValue(v)
+		if !ok {
+			panic(errors.New("failed to find value in object map during serialization"))
+		}
+		vals[i+cnt] = objIdx
+	}
+
+	for _, v := range vals {
+		p.writeSizedInt(v, int(p.trailer.ObjectRefSize))
+	}
+}
+
+func (p *bplistGenerator) writeArrayTag(arr []cfValue) {
+	p.writeCountedTag(bpTagArray, uint64(len(arr)))
+	for _, v := range arr {
+		objIdx, ok := p.indexForPlistValue(v)
+		if !ok {
+			panic(errors.New("failed to find value in object map during serialization"))
+		}
+
+		p.writeSizedInt(objIdx, int(p.trailer.ObjectRefSize))
+	}
+}
+
+func (p *bplistGenerator) Indent(i string) {
+	// There's nothing to indent.
+}
+
+func newBplistGenerator(w io.Writer) *bplistGenerator {
+	return &bplistGenerator{
+		writer: &countedWriter{Writer: mustWriter{w}},
+	}
+}

+ 353 - 0
vendor/github.com/DHowett/go-plist/bplist_parser.go

@@ -0,0 +1,353 @@
+package plist
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"math"
+	"runtime"
+	"time"
+	"unicode/utf16"
+)
+
+const (
+	signedHighBits = 0xFFFFFFFFFFFFFFFF
+)
+
+type offset uint64
+
+type bplistParser struct {
+	buffer []byte
+
+	reader        io.ReadSeeker
+	version       int
+	objects       []cfValue // object ID to object
+	trailer       bplistTrailer
+	trailerOffset uint64
+
+	containerStack []offset // slice of object offsets; manipulated during container deserialization
+}
+
+func (p *bplistParser) validateDocumentTrailer() {
+	if p.trailer.OffsetTableOffset >= p.trailerOffset {
+		panic(fmt.Errorf("offset table beyond beginning of trailer (0x%x, trailer@0x%x)", p.trailer.OffsetTableOffset, p.trailerOffset))
+	}
+
+	if p.trailer.OffsetTableOffset < 9 {
+		panic(fmt.Errorf("offset table begins inside header (0x%x)", p.trailer.OffsetTableOffset))
+	}
+
+	if p.trailerOffset > (p.trailer.NumObjects*uint64(p.trailer.OffsetIntSize))+p.trailer.OffsetTableOffset {
+		panic(errors.New("garbage between offset table and trailer"))
+	}
+
+	if p.trailer.OffsetTableOffset+(uint64(p.trailer.OffsetIntSize)*p.trailer.NumObjects) > p.trailerOffset {
+		panic(errors.New("offset table isn't long enough to address every object"))
+	}
+
+	maxObjectRef := uint64(1) << (8 * p.trailer.ObjectRefSize)
+	if p.trailer.NumObjects > maxObjectRef {
+		panic(fmt.Errorf("more objects (%v) than object ref size (%v bytes) can support", p.trailer.NumObjects, p.trailer.ObjectRefSize))
+	}
+
+	if p.trailer.OffsetIntSize < uint8(8) && (uint64(1)<<(8*p.trailer.OffsetIntSize)) <= p.trailer.OffsetTableOffset {
+		panic(errors.New("offset size isn't big enough to address entire file"))
+	}
+
+	if p.trailer.TopObject >= p.trailer.NumObjects {
+		panic(fmt.Errorf("top object #%d is out of range (only %d exist)", p.trailer.TopObject, p.trailer.NumObjects))
+	}
+}
+
+func (p *bplistParser) parseDocument() (pval cfValue, parseError error) {
+	defer func() {
+		if r := recover(); r != nil {
+			if _, ok := r.(runtime.Error); ok {
+				panic(r)
+			}
+
+			parseError = plistParseError{"binary", r.(error)}
+		}
+	}()
+
+	p.buffer, _ = ioutil.ReadAll(p.reader)
+
+	l := len(p.buffer)
+	if l < 40 {
+		panic(errors.New("not enough data"))
+	}
+
+	if !bytes.Equal(p.buffer[0:6], []byte{'b', 'p', 'l', 'i', 's', 't'}) {
+		panic(errors.New("incomprehensible magic"))
+	}
+
+	p.version = int(((p.buffer[6] - '0') * 10) + (p.buffer[7] - '0'))
+
+	if p.version > 1 {
+		panic(fmt.Errorf("unexpected version %d", p.version))
+	}
+
+	p.trailerOffset = uint64(l - 32)
+	p.trailer = bplistTrailer{
+		SortVersion:       p.buffer[p.trailerOffset+5],
+		OffsetIntSize:     p.buffer[p.trailerOffset+6],
+		ObjectRefSize:     p.buffer[p.trailerOffset+7],
+		NumObjects:        binary.BigEndian.Uint64(p.buffer[p.trailerOffset+8:]),
+		TopObject:         binary.BigEndian.Uint64(p.buffer[p.trailerOffset+16:]),
+		OffsetTableOffset: binary.BigEndian.Uint64(p.buffer[p.trailerOffset+24:]),
+	}
+
+	p.validateDocumentTrailer()
+
+	// INVARIANTS:
+	// - Entire offset table is before trailer
+	// - Offset table begins after header
+	// - Offset table can address entire document
+	// - Object IDs are big enough to support the number of objects in this plist
+	// - Top object is in range
+
+	p.objects = make([]cfValue, p.trailer.NumObjects)
+
+	pval = p.objectAtIndex(p.trailer.TopObject)
+	return
+}
+
+// parseSizedInteger returns a 128-bit integer as low64, high64
+func (p *bplistParser) parseSizedInteger(off offset, nbytes int) (lo uint64, hi uint64, newOffset offset) {
+	// Per comments in CoreFoundation, format version 00 requires that all
+	// 1, 2 or 4-byte integers be interpreted as unsigned. 8-byte integers are
+	// signed (always?) and therefore must be sign extended here.
+	// negative 1, 2, or 4-byte integers are always emitted as 64-bit.
+	switch nbytes {
+	case 1:
+		lo, hi = uint64(p.buffer[off]), 0
+	case 2:
+		lo, hi = uint64(binary.BigEndian.Uint16(p.buffer[off:])), 0
+	case 4:
+		lo, hi = uint64(binary.BigEndian.Uint32(p.buffer[off:])), 0
+	case 8:
+		lo = binary.BigEndian.Uint64(p.buffer[off:])
+		if p.buffer[off]&0x80 != 0 {
+			// sign extend if lo is signed
+			hi = signedHighBits
+		}
+	case 16:
+		lo, hi = binary.BigEndian.Uint64(p.buffer[off+8:]), binary.BigEndian.Uint64(p.buffer[off:])
+	default:
+		panic(errors.New("illegal integer size"))
+	}
+	newOffset = off + offset(nbytes)
+	return
+}
+
+func (p *bplistParser) parseObjectRefAtOffset(off offset) (uint64, offset) {
+	oid, _, next := p.parseSizedInteger(off, int(p.trailer.ObjectRefSize))
+	return oid, next
+}
+
+func (p *bplistParser) parseOffsetAtOffset(off offset) (offset, offset) {
+	parsedOffset, _, next := p.parseSizedInteger(off, int(p.trailer.OffsetIntSize))
+	return offset(parsedOffset), next
+}
+
+func (p *bplistParser) objectAtIndex(index uint64) cfValue {
+	if index >= p.trailer.NumObjects {
+		panic(fmt.Errorf("invalid object#%d (max %d)", index, p.trailer.NumObjects))
+	}
+
+	if pval := p.objects[index]; pval != nil {
+		return pval
+	}
+
+	off, _ := p.parseOffsetAtOffset(offset(p.trailer.OffsetTableOffset + (index * uint64(p.trailer.OffsetIntSize))))
+	if off > offset(p.trailer.OffsetTableOffset-1) {
+		panic(fmt.Errorf("object#%d starts beyond beginning of object table (0x%x, table@0x%x)", index, off, p.trailer.OffsetTableOffset))
+	}
+
+	pval := p.parseTagAtOffset(off)
+	p.objects[index] = pval
+	return pval
+
+}
+
+func (p *bplistParser) pushNestedObject(off offset) {
+	for _, v := range p.containerStack {
+		if v == off {
+			p.panicNestedObject(off)
+		}
+	}
+	p.containerStack = append(p.containerStack, off)
+}
+
+func (p *bplistParser) panicNestedObject(off offset) {
+	ids := ""
+	for _, v := range p.containerStack {
+		ids += fmt.Sprintf("0x%x > ", v)
+	}
+
+	// %s0x%d: ids above ends with " > "
+	panic(fmt.Errorf("self-referential collection@0x%x (%s0x%x) cannot be deserialized", off, ids, off))
+}
+
+func (p *bplistParser) popNestedObject() {
+	p.containerStack = p.containerStack[:len(p.containerStack)-1]
+}
+
+func (p *bplistParser) parseTagAtOffset(off offset) cfValue {
+	tag := p.buffer[off]
+
+	switch tag & 0xF0 {
+	case bpTagNull:
+		switch tag & 0x0F {
+		case bpTagBoolTrue, bpTagBoolFalse:
+			return cfBoolean(tag == bpTagBoolTrue)
+		}
+	case bpTagInteger:
+		lo, hi, _ := p.parseIntegerAtOffset(off)
+		return &cfNumber{
+			signed: hi == signedHighBits, // a signed integer is stored as a 128-bit integer with the top 64 bits set
+			value:  lo,
+		}
+	case bpTagReal:
+		nbytes := 1 << (tag & 0x0F)
+		switch nbytes {
+		case 4:
+			bits := binary.BigEndian.Uint32(p.buffer[off+1:])
+			return &cfReal{wide: false, value: float64(math.Float32frombits(bits))}
+		case 8:
+			bits := binary.BigEndian.Uint64(p.buffer[off+1:])
+			return &cfReal{wide: true, value: math.Float64frombits(bits)}
+		}
+		panic(errors.New("illegal float size"))
+	case bpTagDate:
+		bits := binary.BigEndian.Uint64(p.buffer[off+1:])
+		val := math.Float64frombits(bits)
+
+		// Apple Epoch is 20110101000000Z
+		// Adjust for UNIX Time
+		val += 978307200
+
+		sec, fsec := math.Modf(val)
+		time := time.Unix(int64(sec), int64(fsec*float64(time.Second))).In(time.UTC)
+		return cfDate(time)
+	case bpTagData:
+		data := p.parseDataAtOffset(off)
+		return cfData(data)
+	case bpTagASCIIString:
+		str := p.parseASCIIStringAtOffset(off)
+		return cfString(str)
+	case bpTagUTF16String:
+		str := p.parseUTF16StringAtOffset(off)
+		return cfString(str)
+	case bpTagUID: // Somehow different than int: low half is nbytes - 1 instead of log2(nbytes)
+		lo, _, _ := p.parseSizedInteger(off+1, int(tag&0xF)+1)
+		return cfUID(lo)
+	case bpTagDictionary:
+		return p.parseDictionaryAtOffset(off)
+	case bpTagArray:
+		return p.parseArrayAtOffset(off)
+	}
+	panic(fmt.Errorf("unexpected atom 0x%2.02x at offset 0x%x", tag, off))
+}
+
+func (p *bplistParser) parseIntegerAtOffset(off offset) (uint64, uint64, offset) {
+	tag := p.buffer[off]
+	return p.parseSizedInteger(off+1, 1<<(tag&0xF))
+}
+
+func (p *bplistParser) countForTagAtOffset(off offset) (uint64, offset) {
+	tag := p.buffer[off]
+	cnt := uint64(tag & 0x0F)
+	if cnt == 0xF {
+		cnt, _, off = p.parseIntegerAtOffset(off + 1)
+		return cnt, off
+	}
+	return cnt, off + 1
+}
+
+func (p *bplistParser) parseDataAtOffset(off offset) []byte {
+	len, start := p.countForTagAtOffset(off)
+	if start+offset(len) > offset(p.trailer.OffsetTableOffset) {
+		panic(fmt.Errorf("data@0x%x too long (%v bytes, max is %v)", off, len, p.trailer.OffsetTableOffset-uint64(start)))
+	}
+	return p.buffer[start : start+offset(len)]
+}
+
+func (p *bplistParser) parseASCIIStringAtOffset(off offset) string {
+	len, start := p.countForTagAtOffset(off)
+	if start+offset(len) > offset(p.trailer.OffsetTableOffset) {
+		panic(fmt.Errorf("ascii string@0x%x too long (%v bytes, max is %v)", off, len, p.trailer.OffsetTableOffset-uint64(start)))
+	}
+
+	return zeroCopy8BitString(p.buffer, int(start), int(len))
+}
+
+func (p *bplistParser) parseUTF16StringAtOffset(off offset) string {
+	len, start := p.countForTagAtOffset(off)
+	bytes := len * 2
+	if start+offset(bytes) > offset(p.trailer.OffsetTableOffset) {
+		panic(fmt.Errorf("utf16 string@0x%x too long (%v bytes, max is %v)", off, bytes, p.trailer.OffsetTableOffset-uint64(start)))
+	}
+
+	u16s := make([]uint16, len)
+	for i := offset(0); i < offset(len); i++ {
+		u16s[i] = binary.BigEndian.Uint16(p.buffer[start+(i*2):])
+	}
+	runes := utf16.Decode(u16s)
+	return string(runes)
+}
+
+func (p *bplistParser) parseObjectListAtOffset(off offset, count uint64) []cfValue {
+	if off+offset(count*uint64(p.trailer.ObjectRefSize)) > offset(p.trailer.OffsetTableOffset) {
+		panic(fmt.Errorf("list@0x%x length (%v) puts its end beyond the offset table at 0x%x", off, count, p.trailer.OffsetTableOffset))
+	}
+	objects := make([]cfValue, count)
+
+	next := off
+	var oid uint64
+	for i := uint64(0); i < count; i++ {
+		oid, next = p.parseObjectRefAtOffset(next)
+		objects[i] = p.objectAtIndex(oid)
+	}
+
+	return objects
+}
+
+func (p *bplistParser) parseDictionaryAtOffset(off offset) *cfDictionary {
+	p.pushNestedObject(off)
+	defer p.popNestedObject()
+
+	// a dictionary is an object list of [key key key val val val]
+	cnt, start := p.countForTagAtOffset(off)
+	objects := p.parseObjectListAtOffset(start, cnt*2)
+
+	keys := make([]string, cnt)
+	for i := uint64(0); i < cnt; i++ {
+		if str, ok := objects[i].(cfString); ok {
+			keys[i] = string(str)
+		} else {
+			panic(fmt.Errorf("dictionary@0x%x contains non-string key at index %d", off, i))
+		}
+	}
+
+	return &cfDictionary{
+		keys:   keys,
+		values: objects[cnt:],
+	}
+}
+
+func (p *bplistParser) parseArrayAtOffset(off offset) *cfArray {
+	p.pushNestedObject(off)
+	defer p.popNestedObject()
+
+	// an array is just an object list
+	cnt, start := p.countForTagAtOffset(off)
+	return &cfArray{p.parseObjectListAtOffset(start, cnt)}
+}
+
+func newBplistParser(r io.ReadSeeker) *bplistParser {
+	return &bplistParser{reader: r}
+}

+ 119 - 0
vendor/github.com/DHowett/go-plist/decode.go

@@ -0,0 +1,119 @@
+package plist
+
+import (
+	"bytes"
+	"io"
+	"reflect"
+	"runtime"
+)
+
+type parser interface {
+	parseDocument() (cfValue, error)
+}
+
+// A Decoder reads a property list from an input stream.
+type Decoder struct {
+	// the format of the most-recently-decoded property list
+	Format int
+
+	reader io.ReadSeeker
+	lax    bool
+}
+
+// Decode works like Unmarshal, except it reads the decoder stream to find property list elements.
+//
+// After Decoding, the Decoder's Format field will be set to one of the plist format constants.
+func (p *Decoder) Decode(v interface{}) (err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			if _, ok := r.(runtime.Error); ok {
+				panic(r)
+			}
+			err = r.(error)
+		}
+	}()
+
+	header := make([]byte, 6)
+	p.reader.Read(header)
+	p.reader.Seek(0, 0)
+
+	var parser parser
+	var pval cfValue
+	if bytes.Equal(header, []byte("bplist")) {
+		parser = newBplistParser(p.reader)
+		pval, err = parser.parseDocument()
+		if err != nil {
+			// Had a bplist header, but still got an error: we have to die here.
+			return err
+		}
+		p.Format = BinaryFormat
+	} else {
+		parser = newXMLPlistParser(p.reader)
+		pval, err = parser.parseDocument()
+		if _, ok := err.(invalidPlistError); ok {
+			// Rewind: the XML parser might have exhausted the file.
+			p.reader.Seek(0, 0)
+			// We don't use parser here because we want the textPlistParser type
+			tp := newTextPlistParser(p.reader)
+			pval, err = tp.parseDocument()
+			if err != nil {
+				return err
+			}
+			p.Format = tp.format
+			if p.Format == OpenStepFormat {
+				// OpenStep property lists can only store strings,
+				// so we have to turn on lax mode here for the unmarshal step later.
+				p.lax = true
+			}
+		} else {
+			if err != nil {
+				return err
+			}
+			p.Format = XMLFormat
+		}
+	}
+
+	p.unmarshal(pval, reflect.ValueOf(v))
+	return
+}
+
+// NewDecoder returns a Decoder that reads property list elements from a stream reader, r.
+// NewDecoder requires a Seekable stream for the purposes of file type detection.
+func NewDecoder(r io.ReadSeeker) *Decoder {
+	return &Decoder{Format: InvalidFormat, reader: r, lax: false}
+}
+
+// Unmarshal parses a property list document and stores the result in the value pointed to by v.
+//
+// Unmarshal uses the inverse of the type encodings that Marshal uses, allocating heap-borne types as necessary.
+//
+// When given a nil pointer, Unmarshal allocates a new value for it to point to.
+//
+// To decode property list values into an interface value, Unmarshal decodes the property list into the concrete value contained
+// in the interface value. If the interface value is nil, Unmarshal stores one of the following in the interface value:
+//
+//     string, bool, uint64, float64
+//     plist.UID for "CoreFoundation Keyed Archiver UIDs" (convertible to uint64)
+//     []byte, for plist data
+//     []interface{}, for plist arrays
+//     map[string]interface{}, for plist dictionaries
+//
+// If a property list value is not appropriate for a given value type, Unmarshal aborts immediately and returns an error.
+//
+// As Go does not support 128-bit types, and we don't want to pretend we're giving the user integer types (as opposed to
+// secretly passing them structs), Unmarshal will drop the high 64 bits of any 128-bit integers encoded in binary property lists.
+// (This is important because CoreFoundation serializes some large 64-bit values as 128-bit values with an empty high half.)
+//
+// When Unmarshal encounters an OpenStep property list, it will enter a relaxed parsing mode: OpenStep property lists can only store
+// plain old data as strings, so we will attempt to recover integer, floating-point, boolean and date values wherever they are necessary.
+// (for example, if Unmarshal attempts to unmarshal an OpenStep property list into a time.Time, it will try to parse the string it
+// receives as a time.)
+//
+// Unmarshal returns the detected property list format and an error, if any.
+func Unmarshal(data []byte, v interface{}) (format int, err error) {
+	r := bytes.NewReader(data)
+	dec := NewDecoder(r)
+	err = dec.Decode(v)
+	format = dec.Format
+	return
+}

+ 5 - 0
vendor/github.com/DHowett/go-plist/doc.go

@@ -0,0 +1,5 @@
+// Package plist implements encoding and decoding of Apple's "property list" format.
+// Property lists come in three sorts: plain text (GNUStep and OpenStep), XML and binary.
+// plist supports all of them.
+// The mapping between property list and Go objects is described in the documentation for the Marshal and Unmarshal functions.
+package plist

+ 126 - 0
vendor/github.com/DHowett/go-plist/encode.go

@@ -0,0 +1,126 @@
+package plist
+
+import (
+	"bytes"
+	"errors"
+	"io"
+	"reflect"
+	"runtime"
+)
+
+type generator interface {
+	generateDocument(cfValue)
+	Indent(string)
+}
+
+// An Encoder writes a property list to an output stream.
+type Encoder struct {
+	writer io.Writer
+	format int
+
+	indent string
+}
+
+// Encode writes the property list encoding of v to the stream.
+func (p *Encoder) Encode(v interface{}) (err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			if _, ok := r.(runtime.Error); ok {
+				panic(r)
+			}
+			err = r.(error)
+		}
+	}()
+
+	pval := p.marshal(reflect.ValueOf(v))
+	if pval == nil {
+		panic(errors.New("plist: no root element to encode"))
+	}
+
+	var g generator
+	switch p.format {
+	case XMLFormat:
+		g = newXMLPlistGenerator(p.writer)
+	case BinaryFormat, AutomaticFormat:
+		g = newBplistGenerator(p.writer)
+	case OpenStepFormat, GNUStepFormat:
+		g = newTextPlistGenerator(p.writer, p.format)
+	}
+	g.Indent(p.indent)
+	g.generateDocument(pval)
+	return
+}
+
+// Indent turns on pretty-printing for the XML and Text property list formats.
+// Each element begins on a new line and is preceded by one or more copies of indent according to its nesting depth.
+func (p *Encoder) Indent(indent string) {
+	p.indent = indent
+}
+
+// NewEncoder returns an Encoder that writes an XML property list to w.
+func NewEncoder(w io.Writer) *Encoder {
+	return NewEncoderForFormat(w, XMLFormat)
+}
+
+// NewEncoderForFormat returns an Encoder that writes a property list to w in the specified format.
+// Pass AutomaticFormat to allow the library to choose the best encoding (currently BinaryFormat).
+func NewEncoderForFormat(w io.Writer, format int) *Encoder {
+	return &Encoder{
+		writer: w,
+		format: format,
+	}
+}
+
+// NewBinaryEncoder returns an Encoder that writes a binary property list to w.
+func NewBinaryEncoder(w io.Writer) *Encoder {
+	return NewEncoderForFormat(w, BinaryFormat)
+}
+
+// Marshal returns the property list encoding of v in the specified format.
+//
+// Pass AutomaticFormat to allow the library to choose the best encoding (currently BinaryFormat).
+//
+// Marshal traverses the value v recursively.
+// Any nil values encountered, other than the root, will be silently discarded as
+// the property list format bears no representation for nil values.
+//
+// Strings, integers of varying size, floats and booleans are encoded unchanged.
+// Strings bearing non-ASCII runes will be encoded differently depending upon the property list format:
+// UTF-8 for XML property lists and UTF-16 for binary property lists.
+//
+// Slice and Array values are encoded as property list arrays, except for
+// []byte values, which are encoded as data.
+//
+// Map values encode as dictionaries. The map's key type must be string; there is no provision for encoding non-string dictionary keys.
+//
+// Struct values are encoded as dictionaries, with only exported fields being serialized. Struct field encoding may be influenced with the use of tags.
+// The tag format is:
+//
+//     `plist:"<key>[,flags...]"`
+//
+// The following flags are supported:
+//
+//     omitempty    Only include the field if it is not set to the zero value for its type.
+//
+// If the key is "-", the field is ignored.
+//
+// Anonymous struct fields are encoded as if their exported fields were exposed via the outer struct.
+//
+// Pointer values encode as the value pointed to.
+//
+// Channel, complex and function values cannot be encoded. Any attempt to do so causes Marshal to return an error.
+func Marshal(v interface{}, format int) ([]byte, error) {
+	return MarshalIndent(v, format, "")
+}
+
+// MarshalIndent works like Marshal, but each property list element
+// begins on a new line and is preceded by one or more copies of indent according to its nesting depth.
+func MarshalIndent(v interface{}, format int, indent string) ([]byte, error) {
+	buf := &bytes.Buffer{}
+	enc := NewEncoderForFormat(buf, format)
+	enc.Indent(indent)
+	if err := enc.Encode(v); err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}

+ 17 - 0
vendor/github.com/DHowett/go-plist/fuzz.go

@@ -0,0 +1,17 @@
+// +build gofuzz
+
+package plist
+
+import (
+	"bytes"
+)
+
+func Fuzz(data []byte) int {
+	buf := bytes.NewReader(data)
+
+	var obj interface{}
+	if err := NewDecoder(buf).Decode(&obj); err != nil {
+		return 0
+	}
+	return 1
+}

+ 186 - 0
vendor/github.com/DHowett/go-plist/marshal.go

@@ -0,0 +1,186 @@
+package plist
+
+import (
+	"encoding"
+	"reflect"
+	"time"
+)
+
+func isEmptyValue(v reflect.Value) bool {
+	switch v.Kind() {
+	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+		return v.Len() == 0
+	case reflect.Bool:
+		return !v.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return v.Int() == 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return v.Uint() == 0
+	case reflect.Float32, reflect.Float64:
+		return v.Float() == 0
+	case reflect.Interface, reflect.Ptr:
+		return v.IsNil()
+	}
+	return false
+}
+
+var (
+	plistMarshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
+	textMarshalerType  = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
+	timeType           = reflect.TypeOf((*time.Time)(nil)).Elem()
+)
+
+func implementsInterface(val reflect.Value, interfaceType reflect.Type) (interface{}, bool) {
+	if val.CanInterface() && val.Type().Implements(interfaceType) {
+		return val.Interface(), true
+	}
+
+	if val.CanAddr() {
+		pv := val.Addr()
+		if pv.CanInterface() && pv.Type().Implements(interfaceType) {
+			return pv.Interface(), true
+		}
+	}
+	return nil, false
+}
+
+func (p *Encoder) marshalPlistInterface(marshalable Marshaler) cfValue {
+	value, err := marshalable.MarshalPlist()
+	if err != nil {
+		panic(err)
+	}
+	return p.marshal(reflect.ValueOf(value))
+}
+
+// marshalTextInterface marshals a TextMarshaler to a plist string.
+func (p *Encoder) marshalTextInterface(marshalable encoding.TextMarshaler) cfValue {
+	s, err := marshalable.MarshalText()
+	if err != nil {
+		panic(err)
+	}
+	return cfString(s)
+}
+
+// marshalStruct marshals a reflected struct value to a plist dictionary
+func (p *Encoder) marshalStruct(typ reflect.Type, val reflect.Value) cfValue {
+	tinfo, _ := getTypeInfo(typ)
+
+	dict := &cfDictionary{
+		keys:   make([]string, 0, len(tinfo.fields)),
+		values: make([]cfValue, 0, len(tinfo.fields)),
+	}
+	for _, finfo := range tinfo.fields {
+		value := finfo.value(val)
+		if !value.IsValid() || finfo.omitEmpty && isEmptyValue(value) {
+			continue
+		}
+		dict.keys = append(dict.keys, finfo.name)
+		dict.values = append(dict.values, p.marshal(value))
+	}
+
+	return dict
+}
+
+func (p *Encoder) marshalTime(val reflect.Value) cfValue {
+	time := val.Interface().(time.Time)
+	return cfDate(time)
+}
+
+func (p *Encoder) marshal(val reflect.Value) cfValue {
+	if !val.IsValid() {
+		return nil
+	}
+
+	if receiver, can := implementsInterface(val, plistMarshalerType); can {
+		return p.marshalPlistInterface(receiver.(Marshaler))
+	}
+
+	// time.Time implements TextMarshaler, but we need to store it in RFC3339
+	if val.Type() == timeType {
+		return p.marshalTime(val)
+	}
+	if val.Kind() == reflect.Ptr || (val.Kind() == reflect.Interface && val.NumMethod() == 0) {
+		ival := val.Elem()
+		if ival.IsValid() && ival.Type() == timeType {
+			return p.marshalTime(ival)
+		}
+	}
+
+	// Check for text marshaler.
+	if receiver, can := implementsInterface(val, textMarshalerType); can {
+		return p.marshalTextInterface(receiver.(encoding.TextMarshaler))
+	}
+
+	// Descend into pointers or interfaces
+	if val.Kind() == reflect.Ptr || (val.Kind() == reflect.Interface && val.NumMethod() == 0) {
+		val = val.Elem()
+	}
+
+	// We got this far and still may have an invalid anything or nil ptr/interface
+	if !val.IsValid() || ((val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface) && val.IsNil()) {
+		return nil
+	}
+
+	typ := val.Type()
+
+	if typ == uidType {
+		return cfUID(val.Uint())
+	}
+
+	if val.Kind() == reflect.Struct {
+		return p.marshalStruct(typ, val)
+	}
+
+	switch val.Kind() {
+	case reflect.String:
+		return cfString(val.String())
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return &cfNumber{signed: true, value: uint64(val.Int())}
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return &cfNumber{signed: false, value: val.Uint()}
+	case reflect.Float32:
+		return &cfReal{wide: false, value: val.Float()}
+	case reflect.Float64:
+		return &cfReal{wide: true, value: val.Float()}
+	case reflect.Bool:
+		return cfBoolean(val.Bool())
+	case reflect.Slice, reflect.Array:
+		if typ.Elem().Kind() == reflect.Uint8 {
+			bytes := []byte(nil)
+			if val.CanAddr() {
+				bytes = val.Bytes()
+			} else {
+				bytes = make([]byte, val.Len())
+				reflect.Copy(reflect.ValueOf(bytes), val)
+			}
+			return cfData(bytes)
+		} else {
+			values := make([]cfValue, val.Len())
+			for i, length := 0, val.Len(); i < length; i++ {
+				if subpval := p.marshal(val.Index(i)); subpval != nil {
+					values[i] = subpval
+				}
+			}
+			return &cfArray{values}
+		}
+	case reflect.Map:
+		if typ.Key().Kind() != reflect.String {
+			panic(&unknownTypeError{typ})
+		}
+
+		l := val.Len()
+		dict := &cfDictionary{
+			keys:   make([]string, 0, l),
+			values: make([]cfValue, 0, l),
+		}
+		for _, keyv := range val.MapKeys() {
+			if subpval := p.marshal(val.MapIndex(keyv)); subpval != nil {
+				dict.keys = append(dict.keys, keyv.String())
+				dict.values = append(dict.values, subpval)
+			}
+		}
+		return dict
+	default:
+		panic(&unknownTypeError{typ})
+	}
+}

+ 50 - 0
vendor/github.com/DHowett/go-plist/must.go

@@ -0,0 +1,50 @@
+package plist
+
+import (
+	"io"
+	"strconv"
+)
+
+type mustWriter struct {
+	io.Writer
+}
+
+func (w mustWriter) Write(p []byte) (int, error) {
+	n, err := w.Writer.Write(p)
+	if err != nil {
+		panic(err)
+	}
+	return n, nil
+}
+
+func mustParseInt(str string, base, bits int) int64 {
+	i, err := strconv.ParseInt(str, base, bits)
+	if err != nil {
+		panic(err)
+	}
+	return i
+}
+
+func mustParseUint(str string, base, bits int) uint64 {
+	i, err := strconv.ParseUint(str, base, bits)
+	if err != nil {
+		panic(err)
+	}
+	return i
+}
+
+func mustParseFloat(str string, bits int) float64 {
+	i, err := strconv.ParseFloat(str, bits)
+	if err != nil {
+		panic(err)
+	}
+	return i
+}
+
+func mustParseBool(str string) bool {
+	i, err := strconv.ParseBool(str)
+	if err != nil {
+		panic(err)
+	}
+	return i
+}

+ 85 - 0
vendor/github.com/DHowett/go-plist/plist.go

@@ -0,0 +1,85 @@
+package plist
+
+import (
+	"reflect"
+)
+
+// Property list format constants
+const (
+	// Used by Decoder to represent an invalid property list.
+	InvalidFormat int = 0
+
+	// Used to indicate total abandon with regards to Encoder's output format.
+	AutomaticFormat = 0
+
+	XMLFormat      = 1
+	BinaryFormat   = 2
+	OpenStepFormat = 3
+	GNUStepFormat  = 4
+)
+
+var FormatNames = map[int]string{
+	InvalidFormat:  "unknown/invalid",
+	XMLFormat:      "XML",
+	BinaryFormat:   "Binary",
+	OpenStepFormat: "OpenStep",
+	GNUStepFormat:  "GNUStep",
+}
+
+type unknownTypeError struct {
+	typ reflect.Type
+}
+
+func (u *unknownTypeError) Error() string {
+	return "plist: can't marshal value of type " + u.typ.String()
+}
+
+type invalidPlistError struct {
+	format string
+	err    error
+}
+
+func (e invalidPlistError) Error() string {
+	s := "plist: invalid " + e.format + " property list"
+	if e.err != nil {
+		s += ": " + e.err.Error()
+	}
+	return s
+}
+
+type plistParseError struct {
+	format string
+	err    error
+}
+
+func (e plistParseError) Error() string {
+	s := "plist: error parsing " + e.format + " property list"
+	if e.err != nil {
+		s += ": " + e.err.Error()
+	}
+	return s
+}
+
+// A UID represents a unique object identifier. UIDs are serialized in a manner distinct from
+// that of integers.
+//
+// UIDs cannot be serialized in OpenStepFormat or GNUStepFormat property lists.
+type UID uint64
+
+// Marshaler is the interface implemented by types that can marshal themselves into valid
+// property list objects. The returned value is marshaled in place of the original value
+// implementing Marshaler
+//
+// If an error is returned by MarshalPlist, marshaling stops and the error is returned.
+type Marshaler interface {
+	MarshalPlist() (interface{}, error)
+}
+
+// Unmarshaler is the interface implemented by types that can unmarshal themselves from
+// property list objects. The UnmarshalPlist method receives a function that may
+// be called to unmarshal the original property list value into a field or variable.
+//
+// It is safe to call the unmarshal function more than once.
+type Unmarshaler interface {
+	UnmarshalPlist(unmarshal func(interface{}) error) error
+}

+ 139 - 0
vendor/github.com/DHowett/go-plist/plist_types.go

@@ -0,0 +1,139 @@
+package plist
+
+import (
+	"hash/crc32"
+	"sort"
+	"time"
+)
+
+type cfValue interface {
+	typeName() string
+	hash() interface{}
+}
+
+type cfDictionary struct {
+	keys   sort.StringSlice
+	values []cfValue
+}
+
+func (*cfDictionary) typeName() string {
+	return "dictionary"
+}
+
+func (p *cfDictionary) hash() interface{} {
+	return p
+}
+
+func (p *cfDictionary) Len() int {
+	return len(p.keys)
+}
+
+func (p *cfDictionary) Less(i, j int) bool {
+	return p.keys.Less(i, j)
+}
+
+func (p *cfDictionary) Swap(i, j int) {
+	p.keys.Swap(i, j)
+	p.values[i], p.values[j] = p.values[j], p.values[i]
+}
+
+func (p *cfDictionary) sort() {
+	sort.Sort(p)
+}
+
+type cfArray struct {
+	values []cfValue
+}
+
+func (*cfArray) typeName() string {
+	return "array"
+}
+
+func (p *cfArray) hash() interface{} {
+	return p
+}
+
+type cfString string
+
+func (cfString) typeName() string {
+	return "string"
+}
+
+func (p cfString) hash() interface{} {
+	return string(p)
+}
+
+type cfNumber struct {
+	signed bool
+	value  uint64
+}
+
+func (*cfNumber) typeName() string {
+	return "integer"
+}
+
+func (p *cfNumber) hash() interface{} {
+	if p.signed {
+		return int64(p.value)
+	}
+	return p.value
+}
+
+type cfReal struct {
+	wide  bool
+	value float64
+}
+
+func (cfReal) typeName() string {
+	return "real"
+}
+
+func (p *cfReal) hash() interface{} {
+	if p.wide {
+		return p.value
+	}
+	return float32(p.value)
+}
+
+type cfBoolean bool
+
+func (cfBoolean) typeName() string {
+	return "boolean"
+}
+
+func (p cfBoolean) hash() interface{} {
+	return bool(p)
+}
+
+type cfUID UID
+
+func (cfUID) typeName() string {
+	return "UID"
+}
+
+func (p cfUID) hash() interface{} {
+	return p
+}
+
+type cfData []byte
+
+func (cfData) typeName() string {
+	return "data"
+}
+
+func (p cfData) hash() interface{} {
+	// Data are uniqued by their checksums.
+	// Todo: Look at calculating this only once and storing it somewhere;
+	// crc32 is fairly quick, however.
+	return crc32.ChecksumIEEE([]byte(p))
+}
+
+type cfDate time.Time
+
+func (cfDate) typeName() string {
+	return "date"
+}
+
+func (p cfDate) hash() interface{} {
+	return time.Time(p)
+}

+ 226 - 0
vendor/github.com/DHowett/go-plist/text_generator.go

@@ -0,0 +1,226 @@
+package plist
+
+import (
+	"encoding/hex"
+	"io"
+	"strconv"
+	"time"
+)
+
+type textPlistGenerator struct {
+	writer io.Writer
+	format int
+
+	quotableTable *characterSet
+
+	indent string
+	depth  int
+
+	dictKvDelimiter, dictEntryDelimiter, arrayDelimiter []byte
+}
+
+var (
+	textPlistTimeLayout = "2006-01-02 15:04:05 -0700"
+	padding             = "0000"
+)
+
+func (p *textPlistGenerator) generateDocument(pval cfValue) {
+	p.writePlistValue(pval)
+}
+
+func (p *textPlistGenerator) plistQuotedString(str string) string {
+	if str == "" {
+		return `""`
+	}
+	s := ""
+	quot := false
+	for _, r := range str {
+		if r > 0xFF {
+			quot = true
+			s += `\U`
+			us := strconv.FormatInt(int64(r), 16)
+			s += padding[len(us):]
+			s += us
+		} else if r > 0x7F {
+			quot = true
+			s += `\`
+			us := strconv.FormatInt(int64(r), 8)
+			s += padding[1+len(us):]
+			s += us
+		} else {
+			c := uint8(r)
+			if p.quotableTable.ContainsByte(c) {
+				quot = true
+			}
+
+			switch c {
+			case '\a':
+				s += `\a`
+			case '\b':
+				s += `\b`
+			case '\v':
+				s += `\v`
+			case '\f':
+				s += `\f`
+			case '\\':
+				s += `\\`
+			case '"':
+				s += `\"`
+			case '\t', '\r', '\n':
+				fallthrough
+			default:
+				s += string(c)
+			}
+		}
+	}
+	if quot {
+		s = `"` + s + `"`
+	}
+	return s
+}
+
+func (p *textPlistGenerator) deltaIndent(depthDelta int) {
+	if depthDelta < 0 {
+		p.depth--
+	} else if depthDelta > 0 {
+		p.depth++
+	}
+}
+
+func (p *textPlistGenerator) writeIndent() {
+	if len(p.indent) == 0 {
+		return
+	}
+	if len(p.indent) > 0 {
+		p.writer.Write([]byte("\n"))
+		for i := 0; i < p.depth; i++ {
+			io.WriteString(p.writer, p.indent)
+		}
+	}
+}
+
+func (p *textPlistGenerator) writePlistValue(pval cfValue) {
+	if pval == nil {
+		return
+	}
+
+	switch pval := pval.(type) {
+	case *cfDictionary:
+		pval.sort()
+		p.writer.Write([]byte(`{`))
+		p.deltaIndent(1)
+		for i, k := range pval.keys {
+			p.writeIndent()
+			io.WriteString(p.writer, p.plistQuotedString(k))
+			p.writer.Write(p.dictKvDelimiter)
+			p.writePlistValue(pval.values[i])
+			p.writer.Write(p.dictEntryDelimiter)
+		}
+		p.deltaIndent(-1)
+		p.writeIndent()
+		p.writer.Write([]byte(`}`))
+	case *cfArray:
+		p.writer.Write([]byte(`(`))
+		p.deltaIndent(1)
+		for _, v := range pval.values {
+			p.writeIndent()
+			p.writePlistValue(v)
+			p.writer.Write(p.arrayDelimiter)
+		}
+		p.deltaIndent(-1)
+		p.writeIndent()
+		p.writer.Write([]byte(`)`))
+	case cfString:
+		io.WriteString(p.writer, p.plistQuotedString(string(pval)))
+	case *cfNumber:
+		if p.format == GNUStepFormat {
+			p.writer.Write([]byte(`<*I`))
+		}
+		if pval.signed {
+			io.WriteString(p.writer, strconv.FormatInt(int64(pval.value), 10))
+		} else {
+			io.WriteString(p.writer, strconv.FormatUint(pval.value, 10))
+		}
+		if p.format == GNUStepFormat {
+			p.writer.Write([]byte(`>`))
+		}
+	case *cfReal:
+		if p.format == GNUStepFormat {
+			p.writer.Write([]byte(`<*R`))
+		}
+		// GNUstep does not differentiate between 32/64-bit floats.
+		io.WriteString(p.writer, strconv.FormatFloat(pval.value, 'g', -1, 64))
+		if p.format == GNUStepFormat {
+			p.writer.Write([]byte(`>`))
+		}
+	case cfBoolean:
+		if p.format == GNUStepFormat {
+			if pval {
+				p.writer.Write([]byte(`<*BY>`))
+			} else {
+				p.writer.Write([]byte(`<*BN>`))
+			}
+		} else {
+			if pval {
+				p.writer.Write([]byte(`1`))
+			} else {
+				p.writer.Write([]byte(`0`))
+			}
+		}
+	case cfData:
+		var hexencoded [9]byte
+		var l int
+		var asc = 9
+		hexencoded[8] = ' '
+
+		p.writer.Write([]byte(`<`))
+		b := []byte(pval)
+		for i := 0; i < len(b); i += 4 {
+			l = i + 4
+			if l >= len(b) {
+				l = len(b)
+				// We no longer need the space - or the rest of the buffer.
+				// (we used >= above to get this part without another conditional :P)
+				asc = (l - i) * 2
+			}
+			// Fill the buffer (only up to 8 characters, to preserve the space we implicitly include
+			// at the end of every encode)
+			hex.Encode(hexencoded[:8], b[i:l])
+			io.WriteString(p.writer, string(hexencoded[:asc]))
+		}
+		p.writer.Write([]byte(`>`))
+	case cfDate:
+		if p.format == GNUStepFormat {
+			p.writer.Write([]byte(`<*D`))
+			io.WriteString(p.writer, time.Time(pval).In(time.UTC).Format(textPlistTimeLayout))
+			p.writer.Write([]byte(`>`))
+		} else {
+			io.WriteString(p.writer, p.plistQuotedString(time.Time(pval).In(time.UTC).Format(textPlistTimeLayout)))
+		}
+	}
+}
+
+func (p *textPlistGenerator) Indent(i string) {
+	p.indent = i
+	if i == "" {
+		p.dictKvDelimiter = []byte(`=`)
+	} else {
+		// For pretty-printing
+		p.dictKvDelimiter = []byte(` = `)
+	}
+}
+
+func newTextPlistGenerator(w io.Writer, format int) *textPlistGenerator {
+	table := &osQuotable
+	if format == GNUStepFormat {
+		table = &gsQuotable
+	}
+	return &textPlistGenerator{
+		writer:             mustWriter{w},
+		format:             format,
+		quotableTable:      table,
+		dictKvDelimiter:    []byte(`=`),
+		arrayDelimiter:     []byte(`,`),
+		dictEntryDelimiter: []byte(`;`),
+	}
+}

+ 515 - 0
vendor/github.com/DHowett/go-plist/text_parser.go

@@ -0,0 +1,515 @@
+package plist
+
+import (
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"runtime"
+	"strings"
+	"time"
+	"unicode/utf16"
+	"unicode/utf8"
+)
+
+type textPlistParser struct {
+	reader io.Reader
+	format int
+
+	input string
+	start int
+	pos   int
+	width int
+}
+
+func convertU16(buffer []byte, bo binary.ByteOrder) (string, error) {
+	if len(buffer)%2 != 0 {
+		return "", errors.New("truncated utf16")
+	}
+
+	tmp := make([]uint16, len(buffer)/2)
+	for i := 0; i < len(buffer); i += 2 {
+		tmp[i/2] = bo.Uint16(buffer[i : i+2])
+	}
+	return string(utf16.Decode(tmp)), nil
+}
+
+func guessEncodingAndConvert(buffer []byte) (string, error) {
+	if len(buffer) >= 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF {
+		// UTF-8 BOM
+		return zeroCopy8BitString(buffer, 3, len(buffer)-3), nil
+	} else if len(buffer) >= 2 {
+		// UTF-16 guesses
+
+		switch {
+		// stream is big-endian (BOM is FE FF or head is 00 XX)
+		case (buffer[0] == 0xFE && buffer[1] == 0xFF):
+			return convertU16(buffer[2:], binary.BigEndian)
+		case (buffer[0] == 0 && buffer[1] != 0):
+			return convertU16(buffer, binary.BigEndian)
+
+		// stream is little-endian (BOM is FE FF or head is XX 00)
+		case (buffer[0] == 0xFF && buffer[1] == 0xFE):
+			return convertU16(buffer[2:], binary.LittleEndian)
+		case (buffer[0] != 0 && buffer[1] == 0):
+			return convertU16(buffer, binary.LittleEndian)
+		}
+	}
+
+	// fallback: assume ASCII (not great!)
+	return zeroCopy8BitString(buffer, 0, len(buffer)), nil
+}
+
+func (p *textPlistParser) parseDocument() (pval cfValue, parseError error) {
+	defer func() {
+		if r := recover(); r != nil {
+			if _, ok := r.(runtime.Error); ok {
+				panic(r)
+			}
+			// Wrap all non-invalid-plist errors.
+			parseError = plistParseError{"text", r.(error)}
+		}
+	}()
+
+	buffer, err := ioutil.ReadAll(p.reader)
+	if err != nil {
+		panic(err)
+	}
+
+	p.input, err = guessEncodingAndConvert(buffer)
+	if err != nil {
+		panic(err)
+	}
+
+	val := p.parsePlistValue()
+
+	p.skipWhitespaceAndComments()
+	if p.peek() != eof {
+		if _, ok := val.(cfString); !ok {
+			p.error("garbage after end of document")
+		}
+
+		p.start = 0
+		p.pos = 0
+		val = p.parseDictionary(true)
+	}
+
+	pval = val
+
+	return
+}
+
+const eof rune = -1
+
+func (p *textPlistParser) error(e string, args ...interface{}) {
+	line := strings.Count(p.input[:p.pos], "\n")
+	char := p.pos - strings.LastIndex(p.input[:p.pos], "\n") - 1
+	panic(fmt.Errorf("%s at line %d character %d", fmt.Sprintf(e, args...), line, char))
+}
+
+func (p *textPlistParser) next() rune {
+	if int(p.pos) >= len(p.input) {
+		p.width = 0
+		return eof
+	}
+	r, w := utf8.DecodeRuneInString(p.input[p.pos:])
+	p.width = w
+	p.pos += p.width
+	return r
+}
+
+func (p *textPlistParser) backup() {
+	p.pos -= p.width
+}
+
+func (p *textPlistParser) peek() rune {
+	r := p.next()
+	p.backup()
+	return r
+}
+
+func (p *textPlistParser) emit() string {
+	s := p.input[p.start:p.pos]
+	p.start = p.pos
+	return s
+}
+
+func (p *textPlistParser) ignore() {
+	p.start = p.pos
+}
+
+func (p *textPlistParser) empty() bool {
+	return p.start == p.pos
+}
+
+func (p *textPlistParser) scanUntil(ch rune) {
+	if x := strings.IndexRune(p.input[p.pos:], ch); x >= 0 {
+		p.pos += x
+		return
+	}
+	p.pos = len(p.input)
+}
+
+func (p *textPlistParser) scanUntilAny(chs string) {
+	if x := strings.IndexAny(p.input[p.pos:], chs); x >= 0 {
+		p.pos += x
+		return
+	}
+	p.pos = len(p.input)
+}
+
+func (p *textPlistParser) scanCharactersInSet(ch *characterSet) {
+	for ch.Contains(p.next()) {
+	}
+	p.backup()
+}
+
+func (p *textPlistParser) scanCharactersNotInSet(ch *characterSet) {
+	var r rune
+	for {
+		r = p.next()
+		if r == eof || ch.Contains(r) {
+			break
+		}
+	}
+	p.backup()
+}
+
+func (p *textPlistParser) skipWhitespaceAndComments() {
+	for {
+		p.scanCharactersInSet(&whitespace)
+		if strings.HasPrefix(p.input[p.pos:], "//") {
+			p.scanCharactersNotInSet(&newlineCharacterSet)
+		} else if strings.HasPrefix(p.input[p.pos:], "/*") {
+			if x := strings.Index(p.input[p.pos:], "*/"); x >= 0 {
+				p.pos += x + 2 // skip the */ as well
+				continue       // consume more whitespace
+			} else {
+				p.error("unexpected eof in block comment")
+			}
+		} else {
+			break
+		}
+	}
+	p.ignore()
+}
+
+func (p *textPlistParser) parseOctalDigits(max int) uint64 {
+	var val uint64
+
+	for i := 0; i < max; i++ {
+		r := p.next()
+
+		if r >= '0' && r <= '7' {
+			val <<= 3
+			val |= uint64((r - '0'))
+		} else {
+			p.backup()
+			break
+		}
+	}
+	return val
+}
+
+func (p *textPlistParser) parseHexDigits(max int) uint64 {
+	var val uint64
+
+	for i := 0; i < max; i++ {
+		r := p.next()
+
+		if r >= 'a' && r <= 'f' {
+			val <<= 4
+			val |= 10 + uint64((r - 'a'))
+		} else if r >= 'A' && r <= 'F' {
+			val <<= 4
+			val |= 10 + uint64((r - 'A'))
+		} else if r >= '0' && r <= '9' {
+			val <<= 4
+			val |= uint64((r - '0'))
+		} else {
+			p.backup()
+			break
+		}
+	}
+	return val
+}
+
+// the \ has already been consumed
+func (p *textPlistParser) parseEscape() string {
+	var s string
+	switch p.next() {
+	case 'a':
+		s = "\a"
+	case 'b':
+		s = "\b"
+	case 'v':
+		s = "\v"
+	case 'f':
+		s = "\f"
+	case 't':
+		s = "\t"
+	case 'r':
+		s = "\r"
+	case 'n':
+		s = "\n"
+	case '\\':
+		s = `\`
+	case '"':
+		s = `"`
+	case 'x':
+		s = string(rune(p.parseHexDigits(2)))
+	case 'u', 'U':
+		s = string(rune(p.parseHexDigits(4)))
+	case '0', '1', '2', '3', '4', '5', '6', '7':
+		p.backup() // we've already consumed one of the digits
+		s = string(rune(p.parseOctalDigits(3)))
+	default:
+		p.backup() // everything else should be accepted
+	}
+	p.ignore() // skip the entire escape sequence
+	return s
+}
+
+// the " has already been consumed
+func (p *textPlistParser) parseQuotedString() cfString {
+	p.ignore() // ignore the "
+
+	slowPath := false
+	s := ""
+
+	for {
+		p.scanUntilAny(`"\`)
+		switch p.peek() {
+		case eof:
+			p.error("unexpected eof in quoted string")
+		case '"':
+			section := p.emit()
+			p.pos++ // skip "
+			if !slowPath {
+				return cfString(section)
+			} else {
+				s += section
+				return cfString(s)
+			}
+		case '\\':
+			slowPath = true
+			s += p.emit()
+			p.next() // consume \
+			s += p.parseEscape()
+		}
+	}
+}
+
+func (p *textPlistParser) parseUnquotedString() cfString {
+	p.scanCharactersNotInSet(&gsQuotable)
+	s := p.emit()
+	if s == "" {
+		p.error("invalid unquoted string (found an unquoted character that should be quoted?)")
+	}
+
+	return cfString(s)
+}
+
+// the { has already been consumed
+func (p *textPlistParser) parseDictionary(ignoreEof bool) *cfDictionary {
+	//p.ignore() // ignore the {
+	var keypv cfValue
+	keys := make([]string, 0, 32)
+	values := make([]cfValue, 0, 32)
+outer:
+	for {
+		p.skipWhitespaceAndComments()
+
+		switch p.next() {
+		case eof:
+			if !ignoreEof {
+				p.error("unexpected eof in dictionary")
+			}
+			fallthrough
+		case '}':
+			break outer
+		case '"':
+			keypv = p.parseQuotedString()
+		default:
+			p.backup()
+			keypv = p.parseUnquotedString()
+		}
+
+		// INVARIANT: key can't be nil; parseQuoted and parseUnquoted
+		// will panic out before they return nil.
+
+		p.skipWhitespaceAndComments()
+
+		var val cfValue
+		n := p.next()
+		if n == ';' {
+			val = keypv
+		} else if n == '=' {
+			// whitespace is consumed within
+			val = p.parsePlistValue()
+
+			p.skipWhitespaceAndComments()
+
+			if p.next() != ';' {
+				p.error("missing ; in dictionary")
+			}
+		} else {
+			p.error("missing = in dictionary")
+		}
+
+		keys = append(keys, string(keypv.(cfString)))
+		values = append(values, val)
+	}
+
+	return &cfDictionary{keys: keys, values: values}
+}
+
+// the ( has already been consumed
+func (p *textPlistParser) parseArray() *cfArray {
+	//p.ignore() // ignore the (
+	values := make([]cfValue, 0, 32)
+outer:
+	for {
+		p.skipWhitespaceAndComments()
+
+		switch p.next() {
+		case eof:
+			p.error("unexpected eof in array")
+		case ')':
+			break outer // done here
+		case ',':
+			continue // restart; ,) is valid and we don't want to blow it
+		default:
+			p.backup()
+		}
+
+		pval := p.parsePlistValue() // whitespace is consumed within
+		if str, ok := pval.(cfString); ok && string(str) == "" {
+			// Empty strings in arrays are apparently skipped?
+			// TODO: Figure out why this was implemented.
+			continue
+		}
+		values = append(values, pval)
+	}
+	return &cfArray{values}
+}
+
+// the <* have already been consumed
+func (p *textPlistParser) parseGNUStepValue() cfValue {
+	typ := p.next()
+	p.ignore()
+	p.scanUntil('>')
+
+	if typ == eof || typ == '>' || p.empty() || p.peek() == eof {
+		p.error("invalid GNUStep extended value")
+	}
+
+	v := p.emit()
+	p.next() // consume the >
+
+	switch typ {
+	case 'I':
+		if v[0] == '-' {
+			n := mustParseInt(v, 10, 64)
+			return &cfNumber{signed: true, value: uint64(n)}
+		} else {
+			n := mustParseUint(v, 10, 64)
+			return &cfNumber{signed: false, value: n}
+		}
+	case 'R':
+		n := mustParseFloat(v, 64)
+		return &cfReal{wide: true, value: n} // TODO(DH) 32/64
+	case 'B':
+		b := v[0] == 'Y'
+		return cfBoolean(b)
+	case 'D':
+		t, err := time.Parse(textPlistTimeLayout, v)
+		if err != nil {
+			p.error(err.Error())
+		}
+
+		return cfDate(t.In(time.UTC))
+	}
+	p.error("invalid GNUStep type " + string(typ))
+	return nil
+}
+
+// The < has already been consumed
+func (p *textPlistParser) parseHexData() cfData {
+	buf := make([]byte, 256)
+	i := 0
+	c := 0
+
+	for {
+		r := p.next()
+		switch r {
+		case eof:
+			p.error("unexpected eof in data")
+		case '>':
+			if c&1 == 1 {
+				p.error("uneven number of hex digits in data")
+			}
+			p.ignore()
+			return cfData(buf[:i])
+		case ' ', '\t', '\n', '\r', '\u2028', '\u2029': // more lax than apple here: skip spaces
+			continue
+		}
+
+		buf[i] <<= 4
+		if r >= 'a' && r <= 'f' {
+			buf[i] |= 10 + byte((r - 'a'))
+		} else if r >= 'A' && r <= 'F' {
+			buf[i] |= 10 + byte((r - 'A'))
+		} else if r >= '0' && r <= '9' {
+			buf[i] |= byte((r - '0'))
+		} else {
+			p.error("unexpected hex digit `%c'", r)
+		}
+
+		c++
+		if c&1 == 0 {
+			i++
+			if i >= len(buf) {
+				realloc := make([]byte, len(buf)*2)
+				copy(realloc, buf)
+				buf = realloc
+			}
+		}
+	}
+}
+
+func (p *textPlistParser) parsePlistValue() cfValue {
+	for {
+		p.skipWhitespaceAndComments()
+
+		switch p.next() {
+		case eof:
+			return &cfDictionary{}
+		case '<':
+			if p.next() == '*' {
+				p.format = GNUStepFormat
+				return p.parseGNUStepValue()
+			}
+
+			p.backup()
+			return p.parseHexData()
+		case '"':
+			return p.parseQuotedString()
+		case '{':
+			return p.parseDictionary(false)
+		case '(':
+			return p.parseArray()
+		default:
+			p.backup()
+			return p.parseUnquotedString()
+		}
+	}
+}
+
+func newTextPlistParser(r io.Reader) *textPlistParser {
+	return &textPlistParser{
+		reader: r,
+		format: OpenStepFormat,
+	}
+}

+ 43 - 0
vendor/github.com/DHowett/go-plist/text_tables.go

@@ -0,0 +1,43 @@
+package plist
+
+type characterSet [4]uint64
+
+func (s *characterSet) Contains(ch rune) bool {
+	return ch >= 0 && ch <= 255 && s.ContainsByte(byte(ch))
+}
+
+func (s *characterSet) ContainsByte(ch byte) bool {
+	return (s[ch/64]&(1<<(ch%64)) > 0)
+}
+
+// Bitmap of characters that must be inside a quoted string
+// when written to an old-style property list
+// Low bits represent lower characters, and each uint64 represents 64 characters.
+var gsQuotable = characterSet{
+	0x78001385ffffffff,
+	0xa800000138000000,
+	0xffffffffffffffff,
+	0xffffffffffffffff,
+}
+
+// 7f instead of 3f in the top line: CFOldStylePlist.c says . is valid, but they quote it.
+var osQuotable = characterSet{
+	0xf4007f6fffffffff,
+	0xf8000001f8000001,
+	0xffffffffffffffff,
+	0xffffffffffffffff,
+}
+
+var whitespace = characterSet{
+	0x0000000100003f00,
+	0x0000000000000000,
+	0x0000000000000000,
+	0x0000000000000000,
+}
+
+var newlineCharacterSet = characterSet{
+	0x0000000000002400,
+	0x0000000000000000,
+	0x0000000000000000,
+	0x0000000000000000,
+}

+ 170 - 0
vendor/github.com/DHowett/go-plist/typeinfo.go

@@ -0,0 +1,170 @@
+package plist
+
+import (
+	"reflect"
+	"strings"
+	"sync"
+)
+
+// typeInfo holds details for the plist representation of a type.
+type typeInfo struct {
+	fields []fieldInfo
+}
+
+// fieldInfo holds details for the plist representation of a single field.
+type fieldInfo struct {
+	idx       []int
+	name      string
+	omitEmpty bool
+}
+
+var tinfoMap = make(map[reflect.Type]*typeInfo)
+var tinfoLock sync.RWMutex
+
+// getTypeInfo returns the typeInfo structure with details necessary
+// for marshalling and unmarshalling typ.
+func getTypeInfo(typ reflect.Type) (*typeInfo, error) {
+	tinfoLock.RLock()
+	tinfo, ok := tinfoMap[typ]
+	tinfoLock.RUnlock()
+	if ok {
+		return tinfo, nil
+	}
+	tinfo = &typeInfo{}
+	if typ.Kind() == reflect.Struct {
+		n := typ.NumField()
+		for i := 0; i < n; i++ {
+			f := typ.Field(i)
+			if f.PkgPath != "" || f.Tag.Get("plist") == "-" {
+				continue // Private field
+			}
+
+			// For embedded structs, embed its fields.
+			if f.Anonymous {
+				t := f.Type
+				if t.Kind() == reflect.Ptr {
+					t = t.Elem()
+				}
+				if t.Kind() == reflect.Struct {
+					inner, err := getTypeInfo(t)
+					if err != nil {
+						return nil, err
+					}
+					for _, finfo := range inner.fields {
+						finfo.idx = append([]int{i}, finfo.idx...)
+						if err := addFieldInfo(typ, tinfo, &finfo); err != nil {
+							return nil, err
+						}
+					}
+					continue
+				}
+			}
+
+			finfo, err := structFieldInfo(typ, &f)
+			if err != nil {
+				return nil, err
+			}
+
+			// Add the field if it doesn't conflict with other fields.
+			if err := addFieldInfo(typ, tinfo, finfo); err != nil {
+				return nil, err
+			}
+		}
+	}
+	tinfoLock.Lock()
+	tinfoMap[typ] = tinfo
+	tinfoLock.Unlock()
+	return tinfo, nil
+}
+
+// structFieldInfo builds and returns a fieldInfo for f.
+func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) {
+	finfo := &fieldInfo{idx: f.Index}
+
+	// Split the tag from the xml namespace if necessary.
+	tag := f.Tag.Get("plist")
+
+	// Parse flags.
+	tokens := strings.Split(tag, ",")
+	tag = tokens[0]
+	if len(tokens) > 1 {
+		tag = tokens[0]
+		for _, flag := range tokens[1:] {
+			switch flag {
+			case "omitempty":
+				finfo.omitEmpty = true
+			}
+		}
+	}
+
+	if tag == "" {
+		// If the name part of the tag is completely empty,
+		// use the field name
+		finfo.name = f.Name
+		return finfo, nil
+	}
+
+	finfo.name = tag
+	return finfo, nil
+}
+
+// addFieldInfo adds finfo to tinfo.fields if there are no
+// conflicts, or if conflicts arise from previous fields that were
+// obtained from deeper embedded structures than finfo. In the latter
+// case, the conflicting entries are dropped.
+// A conflict occurs when the path (parent + name) to a field is
+// itself a prefix of another path, or when two paths match exactly.
+// It is okay for field paths to share a common, shorter prefix.
+func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error {
+	var conflicts []int
+	// First, figure all conflicts. Most working code will have none.
+	for i := range tinfo.fields {
+		oldf := &tinfo.fields[i]
+		if newf.name == oldf.name {
+			conflicts = append(conflicts, i)
+		}
+	}
+
+	// Without conflicts, add the new field and return.
+	if conflicts == nil {
+		tinfo.fields = append(tinfo.fields, *newf)
+		return nil
+	}
+
+	// If any conflict is shallower, ignore the new field.
+	// This matches the Go field resolution on embedding.
+	for _, i := range conflicts {
+		if len(tinfo.fields[i].idx) < len(newf.idx) {
+			return nil
+		}
+	}
+
+	// Otherwise, the new field is shallower, and thus takes precedence,
+	// so drop the conflicting fields from tinfo and append the new one.
+	for c := len(conflicts) - 1; c >= 0; c-- {
+		i := conflicts[c]
+		copy(tinfo.fields[i:], tinfo.fields[i+1:])
+		tinfo.fields = tinfo.fields[:len(tinfo.fields)-1]
+	}
+	tinfo.fields = append(tinfo.fields, *newf)
+	return nil
+}
+
+// value returns v's field value corresponding to finfo.
+// It's equivalent to v.FieldByIndex(finfo.idx), but initializes
+// and dereferences pointers as necessary.
+func (finfo *fieldInfo) value(v reflect.Value) reflect.Value {
+	for i, x := range finfo.idx {
+		if i > 0 {
+			t := v.Type()
+			if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
+				if v.IsNil() {
+					v.Set(reflect.New(v.Type().Elem()))
+				}
+				v = v.Elem()
+			}
+		}
+		v = v.Field(x)
+	}
+	return v
+}

+ 317 - 0
vendor/github.com/DHowett/go-plist/unmarshal.go

@@ -0,0 +1,317 @@
+package plist
+
+import (
+	"encoding"
+	"fmt"
+	"reflect"
+	"runtime"
+	"time"
+)
+
+type incompatibleDecodeTypeError struct {
+	dest reflect.Type
+	src  string // type name (from cfValue)
+}
+
+func (u *incompatibleDecodeTypeError) Error() string {
+	return fmt.Sprintf("plist: type mismatch: tried to decode plist type `%v' into value of type `%v'", u.src, u.dest)
+}
+
+var (
+	plistUnmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
+	textUnmarshalerType  = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
+	uidType              = reflect.TypeOf(UID(0))
+)
+
+func isEmptyInterface(v reflect.Value) bool {
+	return v.Kind() == reflect.Interface && v.NumMethod() == 0
+}
+
+func (p *Decoder) unmarshalPlistInterface(pval cfValue, unmarshalable Unmarshaler) {
+	err := unmarshalable.UnmarshalPlist(func(i interface{}) (err error) {
+		defer func() {
+			if r := recover(); r != nil {
+				if _, ok := r.(runtime.Error); ok {
+					panic(r)
+				}
+				err = r.(error)
+			}
+		}()
+		p.unmarshal(pval, reflect.ValueOf(i))
+		return
+	})
+
+	if err != nil {
+		panic(err)
+	}
+}
+
+func (p *Decoder) unmarshalTextInterface(pval cfString, unmarshalable encoding.TextUnmarshaler) {
+	err := unmarshalable.UnmarshalText([]byte(pval))
+	if err != nil {
+		panic(err)
+	}
+}
+
+func (p *Decoder) unmarshalTime(pval cfDate, val reflect.Value) {
+	val.Set(reflect.ValueOf(time.Time(pval)))
+}
+
+func (p *Decoder) unmarshalLaxString(s string, val reflect.Value) {
+	switch val.Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		i := mustParseInt(s, 10, 64)
+		val.SetInt(i)
+		return
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		i := mustParseUint(s, 10, 64)
+		val.SetUint(i)
+		return
+	case reflect.Float32, reflect.Float64:
+		f := mustParseFloat(s, 64)
+		val.SetFloat(f)
+		return
+	case reflect.Bool:
+		b := mustParseBool(s)
+		val.SetBool(b)
+		return
+	case reflect.Struct:
+		if val.Type() == timeType {
+			t, err := time.Parse(textPlistTimeLayout, s)
+			if err != nil {
+				panic(err)
+			}
+			val.Set(reflect.ValueOf(t.In(time.UTC)))
+			return
+		}
+		fallthrough
+	default:
+		panic(&incompatibleDecodeTypeError{val.Type(), "string"})
+	}
+}
+
+func (p *Decoder) unmarshal(pval cfValue, val reflect.Value) {
+	if pval == nil {
+		return
+	}
+
+	if val.Kind() == reflect.Ptr {
+		if val.IsNil() {
+			val.Set(reflect.New(val.Type().Elem()))
+		}
+		val = val.Elem()
+	}
+
+	if isEmptyInterface(val) {
+		v := p.valueInterface(pval)
+		val.Set(reflect.ValueOf(v))
+		return
+	}
+
+	incompatibleTypeError := &incompatibleDecodeTypeError{val.Type(), pval.typeName()}
+
+	// time.Time implements TextMarshaler, but we need to parse it as RFC3339
+	if date, ok := pval.(cfDate); ok {
+		if val.Type() == timeType {
+			p.unmarshalTime(date, val)
+			return
+		}
+		panic(incompatibleTypeError)
+	}
+
+	if receiver, can := implementsInterface(val, plistUnmarshalerType); can {
+		p.unmarshalPlistInterface(pval, receiver.(Unmarshaler))
+		return
+	}
+
+	if val.Type() != timeType {
+		if receiver, can := implementsInterface(val, textUnmarshalerType); can {
+			if str, ok := pval.(cfString); ok {
+				p.unmarshalTextInterface(str, receiver.(encoding.TextUnmarshaler))
+			} else {
+				panic(incompatibleTypeError)
+			}
+			return
+		}
+	}
+
+	typ := val.Type()
+
+	switch pval := pval.(type) {
+	case cfString:
+		if val.Kind() == reflect.String {
+			val.SetString(string(pval))
+			return
+		}
+		if p.lax {
+			p.unmarshalLaxString(string(pval), val)
+			return
+		}
+
+		panic(incompatibleTypeError)
+	case *cfNumber:
+		switch val.Kind() {
+		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+			val.SetInt(int64(pval.value))
+		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+			val.SetUint(pval.value)
+		default:
+			panic(incompatibleTypeError)
+		}
+	case *cfReal:
+		if val.Kind() == reflect.Float32 || val.Kind() == reflect.Float64 {
+			// TODO: Consider warning on a downcast (storing a 64-bit value in a 32-bit reflect)
+			val.SetFloat(pval.value)
+		} else {
+			panic(incompatibleTypeError)
+		}
+	case cfBoolean:
+		if val.Kind() == reflect.Bool {
+			val.SetBool(bool(pval))
+		} else {
+			panic(incompatibleTypeError)
+		}
+	case cfData:
+		if val.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Uint8 {
+			val.SetBytes([]byte(pval))
+		} else {
+			panic(incompatibleTypeError)
+		}
+	case cfUID:
+		if val.Type() == uidType {
+			val.SetUint(uint64(pval))
+		} else {
+			switch val.Kind() {
+			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+				val.SetInt(int64(pval))
+			case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+				val.SetUint(uint64(pval))
+			default:
+				panic(incompatibleTypeError)
+			}
+		}
+	case *cfArray:
+		p.unmarshalArray(pval, val)
+	case *cfDictionary:
+		p.unmarshalDictionary(pval, val)
+	}
+}
+
+func (p *Decoder) unmarshalArray(a *cfArray, val reflect.Value) {
+	var n int
+	if val.Kind() == reflect.Slice {
+		// Slice of element values.
+		// Grow slice.
+		cnt := len(a.values) + val.Len()
+		if cnt >= val.Cap() {
+			ncap := 2 * cnt
+			if ncap < 4 {
+				ncap = 4
+			}
+			new := reflect.MakeSlice(val.Type(), val.Len(), ncap)
+			reflect.Copy(new, val)
+			val.Set(new)
+		}
+		n = val.Len()
+		val.SetLen(cnt)
+	} else if val.Kind() == reflect.Array {
+		if len(a.values) > val.Cap() {
+			panic(fmt.Errorf("plist: attempted to unmarshal %d values into an array of size %d", len(a.values), val.Cap()))
+		}
+	} else {
+		panic(&incompatibleDecodeTypeError{val.Type(), a.typeName()})
+	}
+
+	// Recur to read element into slice.
+	for _, sval := range a.values {
+		p.unmarshal(sval, val.Index(n))
+		n++
+	}
+	return
+}
+
+func (p *Decoder) unmarshalDictionary(dict *cfDictionary, val reflect.Value) {
+	typ := val.Type()
+	switch val.Kind() {
+	case reflect.Struct:
+		tinfo, err := getTypeInfo(typ)
+		if err != nil {
+			panic(err)
+		}
+
+		entries := make(map[string]cfValue, len(dict.keys))
+		for i, k := range dict.keys {
+			sval := dict.values[i]
+			entries[k] = sval
+		}
+
+		for _, finfo := range tinfo.fields {
+			p.unmarshal(entries[finfo.name], finfo.value(val))
+		}
+	case reflect.Map:
+		if val.IsNil() {
+			val.Set(reflect.MakeMap(typ))
+		}
+
+		for i, k := range dict.keys {
+			sval := dict.values[i]
+
+			keyv := reflect.ValueOf(k).Convert(typ.Key())
+			mapElem := reflect.New(typ.Elem()).Elem()
+
+			p.unmarshal(sval, mapElem)
+			val.SetMapIndex(keyv, mapElem)
+		}
+	default:
+		panic(&incompatibleDecodeTypeError{typ, dict.typeName()})
+	}
+}
+
+/* *Interface is modelled after encoding/json */
+func (p *Decoder) valueInterface(pval cfValue) interface{} {
+	switch pval := pval.(type) {
+	case cfString:
+		return string(pval)
+	case *cfNumber:
+		if pval.signed {
+			return int64(pval.value)
+		}
+		return pval.value
+	case *cfReal:
+		if pval.wide {
+			return pval.value
+		} else {
+			return float32(pval.value)
+		}
+	case cfBoolean:
+		return bool(pval)
+	case *cfArray:
+		return p.arrayInterface(pval)
+	case *cfDictionary:
+		return p.dictionaryInterface(pval)
+	case cfData:
+		return []byte(pval)
+	case cfDate:
+		return time.Time(pval)
+	case cfUID:
+		return UID(pval)
+	}
+	return nil
+}
+
+func (p *Decoder) arrayInterface(a *cfArray) []interface{} {
+	out := make([]interface{}, len(a.values))
+	for i, subv := range a.values {
+		out[i] = p.valueInterface(subv)
+	}
+	return out
+}
+
+func (p *Decoder) dictionaryInterface(dict *cfDictionary) map[string]interface{} {
+	out := make(map[string]interface{})
+	for i, k := range dict.keys {
+		subv := dict.values[i]
+		out[k] = p.valueInterface(subv)
+	}
+	return out
+}

+ 25 - 0
vendor/github.com/DHowett/go-plist/util.go

@@ -0,0 +1,25 @@
+package plist
+
+import "io"
+
+type countedWriter struct {
+	io.Writer
+	nbytes int
+}
+
+func (w *countedWriter) Write(p []byte) (int, error) {
+	n, err := w.Writer.Write(p)
+	w.nbytes += n
+	return n, err
+}
+
+func (w *countedWriter) BytesWritten() int {
+	return w.nbytes
+}
+
+func unsignedGetBase(s string) (string, int) {
+	if len(s) > 1 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') {
+		return s[2:], 16
+	}
+	return s, 10
+}

+ 185 - 0
vendor/github.com/DHowett/go-plist/xml_generator.go

@@ -0,0 +1,185 @@
+package plist
+
+import (
+	"bufio"
+	"encoding/base64"
+	"encoding/xml"
+	"io"
+	"math"
+	"strconv"
+	"time"
+)
+
+const (
+	xmlHEADER     string = `<?xml version="1.0" encoding="UTF-8"?>` + "\n"
+	xmlDOCTYPE           = `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">` + "\n"
+	xmlArrayTag          = "array"
+	xmlDataTag           = "data"
+	xmlDateTag           = "date"
+	xmlDictTag           = "dict"
+	xmlFalseTag          = "false"
+	xmlIntegerTag        = "integer"
+	xmlKeyTag            = "key"
+	xmlPlistTag          = "plist"
+	xmlRealTag           = "real"
+	xmlStringTag         = "string"
+	xmlTrueTag           = "true"
+
+	// magic value used in the XML encoding of UIDs
+	// (stored as a dictionary mapping CF$UID->integer)
+	xmlCFUIDMagic = "CF$UID"
+)
+
+func formatXMLFloat(f float64) string {
+	switch {
+	case math.IsInf(f, 1):
+		return "inf"
+	case math.IsInf(f, -1):
+		return "-inf"
+	case math.IsNaN(f):
+		return "nan"
+	}
+	return strconv.FormatFloat(f, 'g', -1, 64)
+}
+
+type xmlPlistGenerator struct {
+	*bufio.Writer
+
+	indent     string
+	depth      int
+	putNewline bool
+}
+
+func (p *xmlPlistGenerator) generateDocument(root cfValue) {
+	p.WriteString(xmlHEADER)
+	p.WriteString(xmlDOCTYPE)
+
+	p.openTag(`plist version="1.0"`)
+	p.writePlistValue(root)
+	p.closeTag(xmlPlistTag)
+	p.Flush()
+}
+
+func (p *xmlPlistGenerator) openTag(n string) {
+	p.writeIndent(1)
+	p.WriteByte('<')
+	p.WriteString(n)
+	p.WriteByte('>')
+}
+
+func (p *xmlPlistGenerator) closeTag(n string) {
+	p.writeIndent(-1)
+	p.WriteString("</")
+	p.WriteString(n)
+	p.WriteByte('>')
+}
+
+func (p *xmlPlistGenerator) element(n string, v string) {
+	p.writeIndent(0)
+	if len(v) == 0 {
+		p.WriteByte('<')
+		p.WriteString(n)
+		p.WriteString("/>")
+	} else {
+		p.WriteByte('<')
+		p.WriteString(n)
+		p.WriteByte('>')
+
+		err := xml.EscapeText(p.Writer, []byte(v))
+		if err != nil {
+			panic(err)
+		}
+
+		p.WriteString("</")
+		p.WriteString(n)
+		p.WriteByte('>')
+	}
+}
+
+func (p *xmlPlistGenerator) writeDictionary(dict *cfDictionary) {
+	dict.sort()
+	p.openTag(xmlDictTag)
+	for i, k := range dict.keys {
+		p.element(xmlKeyTag, k)
+		p.writePlistValue(dict.values[i])
+	}
+	p.closeTag(xmlDictTag)
+}
+
+func (p *xmlPlistGenerator) writeArray(a *cfArray) {
+	p.openTag(xmlArrayTag)
+	for _, v := range a.values {
+		p.writePlistValue(v)
+	}
+	p.closeTag(xmlArrayTag)
+}
+
+func (p *xmlPlistGenerator) writePlistValue(pval cfValue) {
+	if pval == nil {
+		return
+	}
+
+	switch pval := pval.(type) {
+	case cfString:
+		p.element(xmlStringTag, string(pval))
+	case *cfNumber:
+		if pval.signed {
+			p.element(xmlIntegerTag, strconv.FormatInt(int64(pval.value), 10))
+		} else {
+			p.element(xmlIntegerTag, strconv.FormatUint(pval.value, 10))
+		}
+	case *cfReal:
+		p.element(xmlRealTag, formatXMLFloat(pval.value))
+	case cfBoolean:
+		if bool(pval) {
+			p.element(xmlTrueTag, "")
+		} else {
+			p.element(xmlFalseTag, "")
+		}
+	case cfData:
+		p.element(xmlDataTag, base64.StdEncoding.EncodeToString([]byte(pval)))
+	case cfDate:
+		p.element(xmlDateTag, time.Time(pval).In(time.UTC).Format(time.RFC3339))
+	case *cfDictionary:
+		p.writeDictionary(pval)
+	case *cfArray:
+		p.writeArray(pval)
+	case cfUID:
+		p.openTag(xmlDictTag)
+		p.element(xmlKeyTag, xmlCFUIDMagic)
+		p.element(xmlIntegerTag, strconv.FormatUint(uint64(pval), 10))
+		p.closeTag(xmlDictTag)
+	}
+}
+
+func (p *xmlPlistGenerator) writeIndent(delta int) {
+	if len(p.indent) == 0 {
+		return
+	}
+
+	if delta < 0 {
+		p.depth--
+	}
+
+	if p.putNewline {
+		// from encoding/xml/marshal.go; it seems to be intended
+		// to suppress the first newline.
+		p.WriteByte('\n')
+	} else {
+		p.putNewline = true
+	}
+	for i := 0; i < p.depth; i++ {
+		p.WriteString(p.indent)
+	}
+	if delta > 0 {
+		p.depth++
+	}
+}
+
+func (p *xmlPlistGenerator) Indent(i string) {
+	p.indent = i
+}
+
+func newXMLPlistGenerator(w io.Writer) *xmlPlistGenerator {
+	return &xmlPlistGenerator{Writer: bufio.NewWriter(w)}
+}

+ 216 - 0
vendor/github.com/DHowett/go-plist/xml_parser.go

@@ -0,0 +1,216 @@
+package plist
+
+import (
+	"encoding/base64"
+	"encoding/xml"
+	"errors"
+	"fmt"
+	"io"
+	"runtime"
+	"strings"
+	"time"
+)
+
+type xmlPlistParser struct {
+	reader             io.Reader
+	xmlDecoder         *xml.Decoder
+	whitespaceReplacer *strings.Replacer
+	ntags              int
+}
+
+func (p *xmlPlistParser) parseDocument() (pval cfValue, parseError error) {
+	defer func() {
+		if r := recover(); r != nil {
+			if _, ok := r.(runtime.Error); ok {
+				panic(r)
+			}
+			if _, ok := r.(invalidPlistError); ok {
+				parseError = r.(error)
+			} else {
+				// Wrap all non-invalid-plist errors.
+				parseError = plistParseError{"XML", r.(error)}
+			}
+		}
+	}()
+	for {
+		if token, err := p.xmlDecoder.Token(); err == nil {
+			if element, ok := token.(xml.StartElement); ok {
+				pval = p.parseXMLElement(element)
+				if p.ntags == 0 {
+					panic(invalidPlistError{"XML", errors.New("no elements encountered")})
+				}
+				return
+			}
+		} else {
+			// The first XML parse turned out to be invalid:
+			// we do not have an XML property list.
+			panic(invalidPlistError{"XML", err})
+		}
+	}
+}
+
+func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) cfValue {
+	var charData xml.CharData
+	switch element.Name.Local {
+	case "plist":
+		p.ntags++
+		for {
+			token, err := p.xmlDecoder.Token()
+			if err != nil {
+				panic(err)
+			}
+
+			if el, ok := token.(xml.EndElement); ok && el.Name.Local == "plist" {
+				break
+			}
+
+			if el, ok := token.(xml.StartElement); ok {
+				return p.parseXMLElement(el)
+			}
+		}
+		return nil
+	case "string":
+		p.ntags++
+		err := p.xmlDecoder.DecodeElement(&charData, &element)
+		if err != nil {
+			panic(err)
+		}
+
+		return cfString(charData)
+	case "integer":
+		p.ntags++
+		err := p.xmlDecoder.DecodeElement(&charData, &element)
+		if err != nil {
+			panic(err)
+		}
+
+		s := string(charData)
+		if len(s) == 0 {
+			panic(errors.New("invalid empty <integer/>"))
+		}
+
+		if s[0] == '-' {
+			s, base := unsignedGetBase(s[1:])
+			n := mustParseInt("-"+s, base, 64)
+			return &cfNumber{signed: true, value: uint64(n)}
+		} else {
+			s, base := unsignedGetBase(s)
+			n := mustParseUint(s, base, 64)
+			return &cfNumber{signed: false, value: n}
+		}
+	case "real":
+		p.ntags++
+		err := p.xmlDecoder.DecodeElement(&charData, &element)
+		if err != nil {
+			panic(err)
+		}
+
+		n := mustParseFloat(string(charData), 64)
+		return &cfReal{wide: true, value: n}
+	case "true", "false":
+		p.ntags++
+		p.xmlDecoder.Skip()
+
+		b := element.Name.Local == "true"
+		return cfBoolean(b)
+	case "date":
+		p.ntags++
+		err := p.xmlDecoder.DecodeElement(&charData, &element)
+		if err != nil {
+			panic(err)
+		}
+
+		t, err := time.ParseInLocation(time.RFC3339, string(charData), time.UTC)
+		if err != nil {
+			panic(err)
+		}
+
+		return cfDate(t)
+	case "data":
+		p.ntags++
+		err := p.xmlDecoder.DecodeElement(&charData, &element)
+		if err != nil {
+			panic(err)
+		}
+
+		str := p.whitespaceReplacer.Replace(string(charData))
+
+		l := base64.StdEncoding.DecodedLen(len(str))
+		bytes := make([]uint8, l)
+		l, err = base64.StdEncoding.Decode(bytes, []byte(str))
+		if err != nil {
+			panic(err)
+		}
+
+		return cfData(bytes[:l])
+	case "dict":
+		p.ntags++
+		var key *string
+		keys := make([]string, 0, 32)
+		values := make([]cfValue, 0, 32)
+		for {
+			token, err := p.xmlDecoder.Token()
+			if err != nil {
+				panic(err)
+			}
+
+			if el, ok := token.(xml.EndElement); ok && el.Name.Local == "dict" {
+				if key != nil {
+					panic(errors.New("missing value in dictionary"))
+				}
+				break
+			}
+
+			if el, ok := token.(xml.StartElement); ok {
+				if el.Name.Local == "key" {
+					var k string
+					p.xmlDecoder.DecodeElement(&k, &el)
+					key = &k
+				} else {
+					if key == nil {
+						panic(errors.New("missing key in dictionary"))
+					}
+					keys = append(keys, *key)
+					values = append(values, p.parseXMLElement(el))
+					key = nil
+				}
+			}
+		}
+
+		if len(keys) == 1 && keys[0] == "CF$UID" && len(values) == 1 {
+			if integer, ok := values[0].(*cfNumber); ok {
+				return cfUID(integer.value)
+			}
+		}
+
+		return &cfDictionary{keys: keys, values: values}
+	case "array":
+		p.ntags++
+		values := make([]cfValue, 0, 10)
+		for {
+			token, err := p.xmlDecoder.Token()
+			if err != nil {
+				panic(err)
+			}
+
+			if el, ok := token.(xml.EndElement); ok && el.Name.Local == "array" {
+				break
+			}
+
+			if el, ok := token.(xml.StartElement); ok {
+				values = append(values, p.parseXMLElement(el))
+			}
+		}
+		return &cfArray{values}
+	}
+	err := fmt.Errorf("encountered unknown element %s", element.Name.Local)
+	if p.ntags == 0 {
+		// If out first XML tag is invalid, it might be an openstep data element, ala <abab> or <0101>
+		panic(invalidPlistError{"XML", err})
+	}
+	panic(err)
+}
+
+func newXMLPlistParser(r io.Reader) *xmlPlistParser {
+	return &xmlPlistParser{r, xml.NewDecoder(r), strings.NewReplacer("\t", "", "\n", "", " ", "", "\r", ""), 0}
+}

+ 20 - 0
vendor/github.com/DHowett/go-plist/zerocopy.go

@@ -0,0 +1,20 @@
+// +build !appengine
+
+package plist
+
+import (
+	"reflect"
+	"unsafe"
+)
+
+func zeroCopy8BitString(buf []byte, off int, len int) string {
+	if len == 0 {
+		return ""
+	}
+
+	var s string
+	hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
+	hdr.Data = uintptr(unsafe.Pointer(&buf[off]))
+	hdr.Len = len
+	return s
+}

+ 7 - 0
vendor/github.com/DHowett/go-plist/zerocopy_appengine.go

@@ -0,0 +1,7 @@
+// +build appengine
+
+package plist
+
+func zeroCopy8BitString(buf []byte, off int, len int) string {
+	return string(buf[off : off+len])
+}