Initial commit and macOS install
This commit is contained in:
commit
cbca29dc62
15
Gopkg.lock
generated
Normal file
15
Gopkg.lock
generated
Normal file
@ -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
Gopkg.toml
Normal file
34
Gopkg.toml
Normal file
@ -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
main.go
Normal file
222
main.go
Normal file
@ -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
truststore_darwin.go
Normal file
98
truststore_darwin.go
Normal file
@ -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
vendor/github.com/DHowett/go-plist/.gitlab-ci.yml
generated
vendored
Normal file
39
vendor/github.com/DHowett/go-plist/.gitlab-ci.yml
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/LICENSE
generated
vendored
Normal file
58
vendor/github.com/DHowett/go-plist/LICENSE
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/README.md
generated
vendored
Normal file
21
vendor/github.com/DHowett/go-plist/README.md
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# plist - A pure Go property list transcoder [](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
vendor/github.com/DHowett/go-plist/bplist.go
generated
vendored
Normal file
26
vendor/github.com/DHowett/go-plist/bplist.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/bplist_generator.go
generated
vendored
Normal file
303
vendor/github.com/DHowett/go-plist/bplist_generator.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/bplist_parser.go
generated
vendored
Normal file
353
vendor/github.com/DHowett/go-plist/bplist_parser.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/decode.go
generated
vendored
Normal file
119
vendor/github.com/DHowett/go-plist/decode.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/doc.go
generated
vendored
Normal file
5
vendor/github.com/DHowett/go-plist/doc.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/encode.go
generated
vendored
Normal file
126
vendor/github.com/DHowett/go-plist/encode.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/fuzz.go
generated
vendored
Normal file
17
vendor/github.com/DHowett/go-plist/fuzz.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/marshal.go
generated
vendored
Normal file
186
vendor/github.com/DHowett/go-plist/marshal.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/must.go
generated
vendored
Normal file
50
vendor/github.com/DHowett/go-plist/must.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/plist.go
generated
vendored
Normal file
85
vendor/github.com/DHowett/go-plist/plist.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/plist_types.go
generated
vendored
Normal file
139
vendor/github.com/DHowett/go-plist/plist_types.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/text_generator.go
generated
vendored
Normal file
226
vendor/github.com/DHowett/go-plist/text_generator.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/text_parser.go
generated
vendored
Normal file
515
vendor/github.com/DHowett/go-plist/text_parser.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/text_tables.go
generated
vendored
Normal file
43
vendor/github.com/DHowett/go-plist/text_tables.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/typeinfo.go
generated
vendored
Normal file
170
vendor/github.com/DHowett/go-plist/typeinfo.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/unmarshal.go
generated
vendored
Normal file
317
vendor/github.com/DHowett/go-plist/unmarshal.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/util.go
generated
vendored
Normal file
25
vendor/github.com/DHowett/go-plist/util.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/xml_generator.go
generated
vendored
Normal file
185
vendor/github.com/DHowett/go-plist/xml_generator.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/xml_parser.go
generated
vendored
Normal file
216
vendor/github.com/DHowett/go-plist/xml_parser.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/zerocopy.go
generated
vendored
Normal file
20
vendor/github.com/DHowett/go-plist/zerocopy.go
generated
vendored
Normal file
@ -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
vendor/github.com/DHowett/go-plist/zerocopy_appengine.go
generated
vendored
Normal file
7
vendor/github.com/DHowett/go-plist/zerocopy_appengine.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// +build appengine
|
||||
|
||||
package plist
|
||||
|
||||
func zeroCopy8BitString(buf []byte, off int, len int) string {
|
||||
return string(buf[off : off+len])
|
||||
}
|
Loading…
Reference in New Issue
Block a user