From e1853e6e70d0b7bb0a5afe8f206c06a0c543b171 Mon Sep 17 00:00:00 2001 From: phinexdaz Date: Mon, 23 Apr 2018 11:25:44 +0800 Subject: [PATCH 1/7] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c335d83 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 phinexdaz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 0efe6c90f9e75dfae17d66580995764af049d1d5 Mon Sep 17 00:00:00 2001 From: John Bryan Sazon Date: Tue, 24 Apr 2018 21:11:48 +0800 Subject: [PATCH 2/7] Make the Appfile struct exported --- parser.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/parser.go b/parser.go index 9426d62..0b64caf 100644 --- a/parser.go +++ b/parser.go @@ -26,7 +26,7 @@ const ( androidExt = ".apk" ) -type appInfo struct { +type AppInfo struct { Name string BundleId string Version string @@ -49,7 +49,7 @@ type iosPlist struct { CFBundleIdentifier string `plist:"CFBundleIdentifier"` } -func NewAppParser(name string) (*appInfo, error) { +func NewAppParser(name string) (*AppInfo, error) { file, err := os.Open(name) if err != nil { return nil, err @@ -125,7 +125,7 @@ func parseAndroidManifest(xmlFile *zip.File) (*androidManifest, error) { return manifest, nil } -func parseApkFile(xmlFile *zip.File) (*appInfo, error) { +func parseApkFile(xmlFile *zip.File) (*AppInfo, error) { if xmlFile == nil { return nil, errors.New("AndroidManifest.xml is not found") } @@ -135,7 +135,7 @@ func parseApkFile(xmlFile *zip.File) (*appInfo, error) { return nil, err } - info := new(appInfo) + info := new(AppInfo) info.BundleId = manifest.Package info.Version = manifest.VersionName info.Build = manifest.VersionCode @@ -162,7 +162,7 @@ func parseApkIconAndLabel(name string) (image.Image, string, error) { return icon, label, nil } -func parseIpaFile(plistFile *zip.File) (*appInfo, error) { +func parseIpaFile(plistFile *zip.File) (*AppInfo, error) { if plistFile == nil { return nil, errors.New("info.plist is not found") } @@ -184,7 +184,7 @@ func parseIpaFile(plistFile *zip.File) (*appInfo, error) { return nil, err } - info := new(appInfo) + info := new(AppInfo) if p.CFBundleDisplayName == "" { info.Name = p.CFBundleName } else { From d7c0dad4e7208018e417b986cd299bea4e5e13e1 Mon Sep 17 00:00:00 2001 From: phinexdaz Date: Wed, 2 May 2018 11:24:50 +0800 Subject: [PATCH 3/7] Create .travis.yml --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..077bb05 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: go +go: + - 1.x +script: + - go test -v ./... From 8f5af9ef81d4f61b51d2e5645a8afdd250162d49 Mon Sep 17 00:00:00 2001 From: phinexdaz Date: Wed, 2 May 2018 11:30:49 +0800 Subject: [PATCH 4/7] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c06fdbb..67d675b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ # ipapk ipa or apk parser written in golang, aims to extract app information +[![Build Status](https://travis-ci.org/phinexdaz/ipapk.svg?branch=master)](https://travis-ci.org/phinexdaz/ipapk) + ## INSTALL $ go get github.com/phinexdaz/ipapk -## USE +## USAGE ```go package main From 879cbca40a99f21bff616663b3b76d44a6608e46 Mon Sep 17 00:00:00 2001 From: John Bryan Sazon Date: Wed, 2 May 2018 11:04:36 +0800 Subject: [PATCH 5/7] Vendor the external packages for stability control --- vendor/github.com/DHowett/go-plist/LICENSE | 58 + vendor/github.com/DHowett/go-plist/README.md | 21 + vendor/github.com/DHowett/go-plist/bplist.go | 26 + .../DHowett/go-plist/bplist_generator.go | 303 +++++ .../DHowett/go-plist/bplist_parser.go | 353 ++++++ vendor/github.com/DHowett/go-plist/decode.go | 119 ++ vendor/github.com/DHowett/go-plist/doc.go | 5 + vendor/github.com/DHowett/go-plist/encode.go | 126 ++ vendor/github.com/DHowett/go-plist/fuzz.go | 17 + vendor/github.com/DHowett/go-plist/marshal.go | 186 +++ vendor/github.com/DHowett/go-plist/must.go | 50 + vendor/github.com/DHowett/go-plist/plist.go | 85 ++ .../DHowett/go-plist/plist_types.go | 139 +++ .../DHowett/go-plist/text_generator.go | 226 ++++ .../DHowett/go-plist/text_parser.go | 515 ++++++++ .../DHowett/go-plist/text_tables.go | 43 + .../github.com/DHowett/go-plist/typeinfo.go | 170 +++ .../github.com/DHowett/go-plist/unmarshal.go | 320 +++++ vendor/github.com/DHowett/go-plist/util.go | 25 + .../DHowett/go-plist/xml_generator.go | 185 +++ .../github.com/DHowett/go-plist/xml_parser.go | 216 ++++ .../github.com/DHowett/go-plist/zerocopy.go | 20 + .../DHowett/go-plist/zerocopy_appengine.go | 7 + vendor/github.com/andrianbdn/iospng/LICENSE | 22 + vendor/github.com/andrianbdn/iospng/README.md | 36 + vendor/github.com/andrianbdn/iospng/iospng.go | 217 ++++ vendor/github.com/pkg/errors/LICENSE | 23 + vendor/github.com/pkg/errors/README.md | 52 + vendor/github.com/pkg/errors/appveyor.yml | 32 + vendor/github.com/pkg/errors/errors.go | 269 ++++ vendor/github.com/pkg/errors/stack.go | 147 +++ .../shogo82148/androidbinary/LICENSE | 21 + .../shogo82148/androidbinary/README.md | 75 ++ .../shogo82148/androidbinary/apk/apk.go | 178 +++ .../shogo82148/androidbinary/apk/apkxml.go | 82 ++ .../shogo82148/androidbinary/common.go | 258 ++++ .../androidbinary/const_fallback.go | 7 + .../shogo82148/androidbinary/const_go17.go | 7 + .../shogo82148/androidbinary/table.go | 1093 +++++++++++++++++ .../shogo82148/androidbinary/xml.go | 259 ++++ vendor/vendor.json | 37 + 41 files changed, 6030 insertions(+) create mode 100644 vendor/github.com/DHowett/go-plist/LICENSE create mode 100644 vendor/github.com/DHowett/go-plist/README.md create mode 100644 vendor/github.com/DHowett/go-plist/bplist.go create mode 100644 vendor/github.com/DHowett/go-plist/bplist_generator.go create mode 100644 vendor/github.com/DHowett/go-plist/bplist_parser.go create mode 100644 vendor/github.com/DHowett/go-plist/decode.go create mode 100644 vendor/github.com/DHowett/go-plist/doc.go create mode 100644 vendor/github.com/DHowett/go-plist/encode.go create mode 100644 vendor/github.com/DHowett/go-plist/fuzz.go create mode 100644 vendor/github.com/DHowett/go-plist/marshal.go create mode 100644 vendor/github.com/DHowett/go-plist/must.go create mode 100644 vendor/github.com/DHowett/go-plist/plist.go create mode 100644 vendor/github.com/DHowett/go-plist/plist_types.go create mode 100644 vendor/github.com/DHowett/go-plist/text_generator.go create mode 100644 vendor/github.com/DHowett/go-plist/text_parser.go create mode 100644 vendor/github.com/DHowett/go-plist/text_tables.go create mode 100644 vendor/github.com/DHowett/go-plist/typeinfo.go create mode 100644 vendor/github.com/DHowett/go-plist/unmarshal.go create mode 100644 vendor/github.com/DHowett/go-plist/util.go create mode 100644 vendor/github.com/DHowett/go-plist/xml_generator.go create mode 100644 vendor/github.com/DHowett/go-plist/xml_parser.go create mode 100644 vendor/github.com/DHowett/go-plist/zerocopy.go create mode 100644 vendor/github.com/DHowett/go-plist/zerocopy_appengine.go create mode 100644 vendor/github.com/andrianbdn/iospng/LICENSE create mode 100644 vendor/github.com/andrianbdn/iospng/README.md create mode 100644 vendor/github.com/andrianbdn/iospng/iospng.go create mode 100644 vendor/github.com/pkg/errors/LICENSE create mode 100644 vendor/github.com/pkg/errors/README.md create mode 100644 vendor/github.com/pkg/errors/appveyor.yml create mode 100644 vendor/github.com/pkg/errors/errors.go create mode 100644 vendor/github.com/pkg/errors/stack.go create mode 100644 vendor/github.com/shogo82148/androidbinary/LICENSE create mode 100644 vendor/github.com/shogo82148/androidbinary/README.md create mode 100644 vendor/github.com/shogo82148/androidbinary/apk/apk.go create mode 100644 vendor/github.com/shogo82148/androidbinary/apk/apkxml.go create mode 100644 vendor/github.com/shogo82148/androidbinary/common.go create mode 100644 vendor/github.com/shogo82148/androidbinary/const_fallback.go create mode 100644 vendor/github.com/shogo82148/androidbinary/const_go17.go create mode 100644 vendor/github.com/shogo82148/androidbinary/table.go create mode 100644 vendor/github.com/shogo82148/androidbinary/xml.go create mode 100644 vendor/vendor.json diff --git a/vendor/github.com/DHowett/go-plist/LICENSE b/vendor/github.com/DHowett/go-plist/LICENSE new file mode 100644 index 0000000..9f6012f --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/LICENSE @@ -0,0 +1,58 @@ +Copyright (c) 2013, Dustin L. Howett. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. + +-------------------------------------------------------------------------------- +Parts of this package were made available under the license covering +the Go language and all attended core libraries. That license follows. +-------------------------------------------------------------------------------- + +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/DHowett/go-plist/README.md b/vendor/github.com/DHowett/go-plist/README.md new file mode 100644 index 0000000..ffc456f --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/README.md @@ -0,0 +1,21 @@ +# plist - A pure Go property list transcoder [![coverage report](https://gitlab.howett.net/DHowett/plist/badges/master/coverage.svg)](https://gitlab.howett.net/DHowett/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"}) +} +``` diff --git a/vendor/github.com/DHowett/go-plist/bplist.go b/vendor/github.com/DHowett/go-plist/bplist.go new file mode 100644 index 0000000..962793a --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/bplist.go @@ -0,0 +1,26 @@ +package plist + +type bplistTrailer struct { + Unused [5]uint8 + SortVersion uint8 + OffsetIntSize uint8 + ObjectRefSize uint8 + NumObjects uint64 + TopObject uint64 + OffsetTableOffset uint64 +} + +const ( + bpTagNull uint8 = 0x00 + bpTagBoolFalse = 0x08 + bpTagBoolTrue = 0x09 + bpTagInteger = 0x10 + bpTagReal = 0x20 + bpTagDate = 0x30 + bpTagData = 0x40 + bpTagASCIIString = 0x50 + bpTagUTF16String = 0x60 + bpTagUID = 0x80 + bpTagArray = 0xA0 + bpTagDictionary = 0xD0 +) diff --git a/vendor/github.com/DHowett/go-plist/bplist_generator.go b/vendor/github.com/DHowett/go-plist/bplist_generator.go new file mode 100644 index 0000000..09ab71b --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/bplist_generator.go @@ -0,0 +1,303 @@ +package plist + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "time" + "unicode/utf16" +) + +func bplistMinimumIntSize(n uint64) int { + switch { + case n <= uint64(0xff): + return 1 + case n <= uint64(0xffff): + return 2 + case n <= uint64(0xffffffff): + return 4 + default: + return 8 + } +} + +func bplistValueShouldUnique(pval cfValue) bool { + switch pval.(type) { + case cfString, *cfNumber, *cfReal, cfDate, cfData: + return true + } + return false +} + +type bplistGenerator struct { + writer *countedWriter + objmap map[interface{}]uint64 // maps pValue.hash()es to object locations + objtable []cfValue + trailer bplistTrailer +} + +func (p *bplistGenerator) flattenPlistValue(pval cfValue) { + key := pval.hash() + if bplistValueShouldUnique(pval) { + if _, ok := p.objmap[key]; ok { + return + } + } + + p.objmap[key] = uint64(len(p.objtable)) + p.objtable = append(p.objtable, pval) + + switch pval := pval.(type) { + case *cfDictionary: + pval.sort() + for _, k := range pval.keys { + p.flattenPlistValue(cfString(k)) + } + for _, v := range pval.values { + p.flattenPlistValue(v) + } + case *cfArray: + for _, v := range pval.values { + p.flattenPlistValue(v) + } + } +} + +func (p *bplistGenerator) indexForPlistValue(pval cfValue) (uint64, bool) { + v, ok := p.objmap[pval.hash()] + return v, ok +} + +func (p *bplistGenerator) generateDocument(root cfValue) { + p.objtable = make([]cfValue, 0, 16) + p.objmap = make(map[interface{}]uint64) + p.flattenPlistValue(root) + + p.trailer.NumObjects = uint64(len(p.objtable)) + p.trailer.ObjectRefSize = uint8(bplistMinimumIntSize(p.trailer.NumObjects)) + + p.writer.Write([]byte("bplist00")) + + offtable := make([]uint64, p.trailer.NumObjects) + for i, pval := range p.objtable { + offtable[i] = uint64(p.writer.BytesWritten()) + p.writePlistValue(pval) + } + + p.trailer.OffsetIntSize = uint8(bplistMinimumIntSize(uint64(p.writer.BytesWritten()))) + p.trailer.TopObject = p.objmap[root.hash()] + p.trailer.OffsetTableOffset = uint64(p.writer.BytesWritten()) + + for _, offset := range offtable { + p.writeSizedInt(offset, int(p.trailer.OffsetIntSize)) + } + + binary.Write(p.writer, binary.BigEndian, p.trailer) +} + +func (p *bplistGenerator) writePlistValue(pval cfValue) { + if pval == nil { + return + } + + switch pval := pval.(type) { + case *cfDictionary: + p.writeDictionaryTag(pval) + case *cfArray: + p.writeArrayTag(pval.values) + case cfString: + p.writeStringTag(string(pval)) + case *cfNumber: + p.writeIntTag(pval.signed, pval.value) + case *cfReal: + if pval.wide { + p.writeRealTag(pval.value, 64) + } else { + p.writeRealTag(pval.value, 32) + } + case cfBoolean: + p.writeBoolTag(bool(pval)) + case cfData: + p.writeDataTag([]byte(pval)) + case cfDate: + p.writeDateTag(time.Time(pval)) + case cfUID: + p.writeUIDTag(UID(pval)) + default: + panic(fmt.Errorf("unknown plist type %t", pval)) + } +} + +func (p *bplistGenerator) writeSizedInt(n uint64, nbytes int) { + var val interface{} + switch nbytes { + case 1: + val = uint8(n) + case 2: + val = uint16(n) + case 4: + val = uint32(n) + case 8: + val = n + default: + panic(errors.New("illegal integer size")) + } + binary.Write(p.writer, binary.BigEndian, val) +} + +func (p *bplistGenerator) writeBoolTag(v bool) { + tag := uint8(bpTagBoolFalse) + if v { + tag = bpTagBoolTrue + } + binary.Write(p.writer, binary.BigEndian, tag) +} + +func (p *bplistGenerator) writeIntTag(signed bool, n uint64) { + var tag uint8 + var val interface{} + switch { + case n <= uint64(0xff): + val = uint8(n) + tag = bpTagInteger | 0x0 + case n <= uint64(0xffff): + val = uint16(n) + tag = bpTagInteger | 0x1 + case n <= uint64(0xffffffff): + val = uint32(n) + tag = bpTagInteger | 0x2 + case n > uint64(0x7fffffffffffffff) && !signed: + // 64-bit values are always *signed* in format 00. + // Any unsigned value that doesn't intersect with the signed + // range must be sign-extended and stored as a SInt128 + val = n + tag = bpTagInteger | 0x4 + default: + val = n + tag = bpTagInteger | 0x3 + } + + binary.Write(p.writer, binary.BigEndian, tag) + if tag&0xF == 0x4 { + // SInt128; in the absence of true 128-bit integers in Go, + // we'll just fake the top half. We only got here because + // we had an unsigned 64-bit int that didn't fit, + // so sign extend it with zeroes. + binary.Write(p.writer, binary.BigEndian, uint64(0)) + } + binary.Write(p.writer, binary.BigEndian, val) +} + +func (p *bplistGenerator) writeUIDTag(u UID) { + nbytes := bplistMinimumIntSize(uint64(u)) + tag := uint8(bpTagUID | (nbytes - 1)) + + binary.Write(p.writer, binary.BigEndian, tag) + p.writeSizedInt(uint64(u), nbytes) +} + +func (p *bplistGenerator) writeRealTag(n float64, bits int) { + var tag uint8 = bpTagReal | 0x3 + var val interface{} = n + if bits == 32 { + val = float32(n) + tag = bpTagReal | 0x2 + } + + binary.Write(p.writer, binary.BigEndian, tag) + binary.Write(p.writer, binary.BigEndian, val) +} + +func (p *bplistGenerator) writeDateTag(t time.Time) { + tag := uint8(bpTagDate) | 0x3 + val := float64(t.In(time.UTC).UnixNano()) / float64(time.Second) + val -= 978307200 // Adjust to Apple Epoch + + binary.Write(p.writer, binary.BigEndian, tag) + binary.Write(p.writer, binary.BigEndian, val) +} + +func (p *bplistGenerator) writeCountedTag(tag uint8, count uint64) { + marker := tag + if count >= 0xF { + marker |= 0xF + } else { + marker |= uint8(count) + } + + binary.Write(p.writer, binary.BigEndian, marker) + + if count >= 0xF { + p.writeIntTag(false, count) + } +} + +func (p *bplistGenerator) writeDataTag(data []byte) { + p.writeCountedTag(bpTagData, uint64(len(data))) + binary.Write(p.writer, binary.BigEndian, data) +} + +func (p *bplistGenerator) writeStringTag(str string) { + for _, r := range str { + if r > 0x7F { + utf16Runes := utf16.Encode([]rune(str)) + p.writeCountedTag(bpTagUTF16String, uint64(len(utf16Runes))) + binary.Write(p.writer, binary.BigEndian, utf16Runes) + return + } + } + + p.writeCountedTag(bpTagASCIIString, uint64(len(str))) + binary.Write(p.writer, binary.BigEndian, []byte(str)) +} + +func (p *bplistGenerator) writeDictionaryTag(dict *cfDictionary) { + // assumption: sorted already; flattenPlistValue did this. + cnt := len(dict.keys) + p.writeCountedTag(bpTagDictionary, uint64(cnt)) + vals := make([]uint64, cnt*2) + for i, k := range dict.keys { + // invariant: keys have already been "uniqued" (as PStrings) + keyIdx, ok := p.objmap[cfString(k).hash()] + if !ok { + panic(errors.New("failed to find key " + k + " in object map during serialization")) + } + vals[i] = keyIdx + } + + for i, v := range dict.values { + // invariant: values have already been "uniqued" + objIdx, ok := p.indexForPlistValue(v) + if !ok { + panic(errors.New("failed to find value in object map during serialization")) + } + vals[i+cnt] = objIdx + } + + for _, v := range vals { + p.writeSizedInt(v, int(p.trailer.ObjectRefSize)) + } +} + +func (p *bplistGenerator) writeArrayTag(arr []cfValue) { + p.writeCountedTag(bpTagArray, uint64(len(arr))) + for _, v := range arr { + objIdx, ok := p.indexForPlistValue(v) + if !ok { + panic(errors.New("failed to find value in object map during serialization")) + } + + p.writeSizedInt(objIdx, int(p.trailer.ObjectRefSize)) + } +} + +func (p *bplistGenerator) Indent(i string) { + // There's nothing to indent. +} + +func newBplistGenerator(w io.Writer) *bplistGenerator { + return &bplistGenerator{ + writer: &countedWriter{Writer: mustWriter{w}}, + } +} diff --git a/vendor/github.com/DHowett/go-plist/bplist_parser.go b/vendor/github.com/DHowett/go-plist/bplist_parser.go new file mode 100644 index 0000000..1825b57 --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/bplist_parser.go @@ -0,0 +1,353 @@ +package plist + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "math" + "runtime" + "time" + "unicode/utf16" +) + +const ( + signedHighBits = 0xFFFFFFFFFFFFFFFF +) + +type offset uint64 + +type bplistParser struct { + buffer []byte + + reader io.ReadSeeker + version int + objects []cfValue // object ID to object + trailer bplistTrailer + trailerOffset uint64 + + containerStack []offset // slice of object offsets; manipulated during container deserialization +} + +func (p *bplistParser) validateDocumentTrailer() { + if p.trailer.OffsetTableOffset >= p.trailerOffset { + panic(fmt.Errorf("offset table beyond beginning of trailer (0x%x, trailer@0x%x)", p.trailer.OffsetTableOffset, p.trailerOffset)) + } + + if p.trailer.OffsetTableOffset < 9 { + panic(fmt.Errorf("offset table begins inside header (0x%x)", p.trailer.OffsetTableOffset)) + } + + if p.trailerOffset > (p.trailer.NumObjects*uint64(p.trailer.OffsetIntSize))+p.trailer.OffsetTableOffset { + panic(errors.New("garbage between offset table and trailer")) + } + + if p.trailer.OffsetTableOffset+(uint64(p.trailer.OffsetIntSize)*p.trailer.NumObjects) > p.trailerOffset { + panic(errors.New("offset table isn't long enough to address every object")) + } + + maxObjectRef := uint64(1) << (8 * p.trailer.ObjectRefSize) + if p.trailer.NumObjects > maxObjectRef { + panic(fmt.Errorf("more objects (%v) than object ref size (%v bytes) can support", p.trailer.NumObjects, p.trailer.ObjectRefSize)) + } + + if p.trailer.OffsetIntSize < uint8(8) && (uint64(1)<<(8*p.trailer.OffsetIntSize)) <= p.trailer.OffsetTableOffset { + panic(errors.New("offset size isn't big enough to address entire file")) + } + + if p.trailer.TopObject >= p.trailer.NumObjects { + panic(fmt.Errorf("top object #%d is out of range (only %d exist)", p.trailer.TopObject, p.trailer.NumObjects)) + } +} + +func (p *bplistParser) parseDocument() (pval cfValue, parseError error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + + parseError = plistParseError{"binary", r.(error)} + } + }() + + p.buffer, _ = ioutil.ReadAll(p.reader) + + l := len(p.buffer) + if l < 40 { + panic(errors.New("not enough data")) + } + + if !bytes.Equal(p.buffer[0:6], []byte{'b', 'p', 'l', 'i', 's', 't'}) { + panic(errors.New("incomprehensible magic")) + } + + p.version = int(((p.buffer[6] - '0') * 10) + (p.buffer[7] - '0')) + + if p.version > 1 { + panic(fmt.Errorf("unexpected version %d", p.version)) + } + + p.trailerOffset = uint64(l - 32) + p.trailer = bplistTrailer{ + SortVersion: p.buffer[p.trailerOffset+5], + OffsetIntSize: p.buffer[p.trailerOffset+6], + ObjectRefSize: p.buffer[p.trailerOffset+7], + NumObjects: binary.BigEndian.Uint64(p.buffer[p.trailerOffset+8:]), + TopObject: binary.BigEndian.Uint64(p.buffer[p.trailerOffset+16:]), + OffsetTableOffset: binary.BigEndian.Uint64(p.buffer[p.trailerOffset+24:]), + } + + p.validateDocumentTrailer() + + // INVARIANTS: + // - Entire offset table is before trailer + // - Offset table begins after header + // - Offset table can address entire document + // - Object IDs are big enough to support the number of objects in this plist + // - Top object is in range + + p.objects = make([]cfValue, p.trailer.NumObjects) + + pval = p.objectAtIndex(p.trailer.TopObject) + return +} + +// parseSizedInteger returns a 128-bit integer as low64, high64 +func (p *bplistParser) parseSizedInteger(off offset, nbytes int) (lo uint64, hi uint64, newOffset offset) { + // Per comments in CoreFoundation, format version 00 requires that all + // 1, 2 or 4-byte integers be interpreted as unsigned. 8-byte integers are + // signed (always?) and therefore must be sign extended here. + // negative 1, 2, or 4-byte integers are always emitted as 64-bit. + switch nbytes { + case 1: + lo, hi = uint64(p.buffer[off]), 0 + case 2: + lo, hi = uint64(binary.BigEndian.Uint16(p.buffer[off:])), 0 + case 4: + lo, hi = uint64(binary.BigEndian.Uint32(p.buffer[off:])), 0 + case 8: + lo = binary.BigEndian.Uint64(p.buffer[off:]) + if p.buffer[off]&0x80 != 0 { + // sign extend if lo is signed + hi = signedHighBits + } + case 16: + lo, hi = binary.BigEndian.Uint64(p.buffer[off+8:]), binary.BigEndian.Uint64(p.buffer[off:]) + default: + panic(errors.New("illegal integer size")) + } + newOffset = off + offset(nbytes) + return +} + +func (p *bplistParser) parseObjectRefAtOffset(off offset) (uint64, offset) { + oid, _, next := p.parseSizedInteger(off, int(p.trailer.ObjectRefSize)) + return oid, next +} + +func (p *bplistParser) parseOffsetAtOffset(off offset) (offset, offset) { + parsedOffset, _, next := p.parseSizedInteger(off, int(p.trailer.OffsetIntSize)) + return offset(parsedOffset), next +} + +func (p *bplistParser) objectAtIndex(index uint64) cfValue { + if index >= p.trailer.NumObjects { + panic(fmt.Errorf("invalid object#%d (max %d)", index, p.trailer.NumObjects)) + } + + if pval := p.objects[index]; pval != nil { + return pval + } + + off, _ := p.parseOffsetAtOffset(offset(p.trailer.OffsetTableOffset + (index * uint64(p.trailer.OffsetIntSize)))) + if off > offset(p.trailer.OffsetTableOffset-1) { + panic(fmt.Errorf("object#%d starts beyond beginning of object table (0x%x, table@0x%x)", index, off, p.trailer.OffsetTableOffset)) + } + + pval := p.parseTagAtOffset(off) + p.objects[index] = pval + return pval + +} + +func (p *bplistParser) pushNestedObject(off offset) { + for _, v := range p.containerStack { + if v == off { + p.panicNestedObject(off) + } + } + p.containerStack = append(p.containerStack, off) +} + +func (p *bplistParser) panicNestedObject(off offset) { + ids := "" + for _, v := range p.containerStack { + ids += fmt.Sprintf("0x%x > ", v) + } + + // %s0x%d: ids above ends with " > " + panic(fmt.Errorf("self-referential collection@0x%x (%s0x%x) cannot be deserialized", off, ids, off)) +} + +func (p *bplistParser) popNestedObject() { + p.containerStack = p.containerStack[:len(p.containerStack)-1] +} + +func (p *bplistParser) parseTagAtOffset(off offset) cfValue { + tag := p.buffer[off] + + switch tag & 0xF0 { + case bpTagNull: + switch tag & 0x0F { + case bpTagBoolTrue, bpTagBoolFalse: + return cfBoolean(tag == bpTagBoolTrue) + } + case bpTagInteger: + lo, hi, _ := p.parseIntegerAtOffset(off) + return &cfNumber{ + signed: hi == signedHighBits, // a signed integer is stored as a 128-bit integer with the top 64 bits set + value: lo, + } + case bpTagReal: + nbytes := 1 << (tag & 0x0F) + switch nbytes { + case 4: + bits := binary.BigEndian.Uint32(p.buffer[off+1:]) + return &cfReal{wide: false, value: float64(math.Float32frombits(bits))} + case 8: + bits := binary.BigEndian.Uint64(p.buffer[off+1:]) + return &cfReal{wide: true, value: math.Float64frombits(bits)} + } + panic(errors.New("illegal float size")) + case bpTagDate: + bits := binary.BigEndian.Uint64(p.buffer[off+1:]) + val := math.Float64frombits(bits) + + // Apple Epoch is 20110101000000Z + // Adjust for UNIX Time + val += 978307200 + + sec, fsec := math.Modf(val) + time := time.Unix(int64(sec), int64(fsec*float64(time.Second))).In(time.UTC) + return cfDate(time) + case bpTagData: + data := p.parseDataAtOffset(off) + return cfData(data) + case bpTagASCIIString: + str := p.parseASCIIStringAtOffset(off) + return cfString(str) + case bpTagUTF16String: + str := p.parseUTF16StringAtOffset(off) + return cfString(str) + case bpTagUID: // Somehow different than int: low half is nbytes - 1 instead of log2(nbytes) + lo, _, _ := p.parseSizedInteger(off+1, int(tag&0xF)+1) + return cfUID(lo) + case bpTagDictionary: + return p.parseDictionaryAtOffset(off) + case bpTagArray: + return p.parseArrayAtOffset(off) + } + panic(fmt.Errorf("unexpected atom 0x%2.02x at offset 0x%x", tag, off)) +} + +func (p *bplistParser) parseIntegerAtOffset(off offset) (uint64, uint64, offset) { + tag := p.buffer[off] + return p.parseSizedInteger(off+1, 1<<(tag&0xF)) +} + +func (p *bplistParser) countForTagAtOffset(off offset) (uint64, offset) { + tag := p.buffer[off] + cnt := uint64(tag & 0x0F) + if cnt == 0xF { + cnt, _, off = p.parseIntegerAtOffset(off + 1) + return cnt, off + } + return cnt, off + 1 +} + +func (p *bplistParser) parseDataAtOffset(off offset) []byte { + len, start := p.countForTagAtOffset(off) + if start+offset(len) > offset(p.trailer.OffsetTableOffset) { + panic(fmt.Errorf("data@0x%x too long (%v bytes, max is %v)", off, len, p.trailer.OffsetTableOffset-uint64(start))) + } + return p.buffer[start : start+offset(len)] +} + +func (p *bplistParser) parseASCIIStringAtOffset(off offset) string { + len, start := p.countForTagAtOffset(off) + if start+offset(len) > offset(p.trailer.OffsetTableOffset) { + panic(fmt.Errorf("ascii string@0x%x too long (%v bytes, max is %v)", off, len, p.trailer.OffsetTableOffset-uint64(start))) + } + + return zeroCopy8BitString(p.buffer, int(start), int(len)) +} + +func (p *bplistParser) parseUTF16StringAtOffset(off offset) string { + len, start := p.countForTagAtOffset(off) + bytes := len * 2 + if start+offset(bytes) > offset(p.trailer.OffsetTableOffset) { + panic(fmt.Errorf("utf16 string@0x%x too long (%v bytes, max is %v)", off, bytes, p.trailer.OffsetTableOffset-uint64(start))) + } + + u16s := make([]uint16, len) + for i := offset(0); i < offset(len); i++ { + u16s[i] = binary.BigEndian.Uint16(p.buffer[start+(i*2):]) + } + runes := utf16.Decode(u16s) + return string(runes) +} + +func (p *bplistParser) parseObjectListAtOffset(off offset, count uint64) []cfValue { + if off+offset(count*uint64(p.trailer.ObjectRefSize)) > offset(p.trailer.OffsetTableOffset) { + panic(fmt.Errorf("list@0x%x length (%v) puts its end beyond the offset table at 0x%x", off, count, p.trailer.OffsetTableOffset)) + } + objects := make([]cfValue, count) + + next := off + var oid uint64 + for i := uint64(0); i < count; i++ { + oid, next = p.parseObjectRefAtOffset(next) + objects[i] = p.objectAtIndex(oid) + } + + return objects +} + +func (p *bplistParser) parseDictionaryAtOffset(off offset) *cfDictionary { + p.pushNestedObject(off) + defer p.popNestedObject() + + // a dictionary is an object list of [key key key val val val] + cnt, start := p.countForTagAtOffset(off) + objects := p.parseObjectListAtOffset(start, cnt*2) + + keys := make([]string, cnt) + for i := uint64(0); i < cnt; i++ { + if str, ok := objects[i].(cfString); ok { + keys[i] = string(str) + } else { + panic(fmt.Errorf("dictionary@0x%x contains non-string key at index %d", off, i)) + } + } + + return &cfDictionary{ + keys: keys, + values: objects[cnt:], + } +} + +func (p *bplistParser) parseArrayAtOffset(off offset) *cfArray { + p.pushNestedObject(off) + defer p.popNestedObject() + + // an array is just an object list + cnt, start := p.countForTagAtOffset(off) + return &cfArray{p.parseObjectListAtOffset(start, cnt)} +} + +func newBplistParser(r io.ReadSeeker) *bplistParser { + return &bplistParser{reader: r} +} diff --git a/vendor/github.com/DHowett/go-plist/decode.go b/vendor/github.com/DHowett/go-plist/decode.go new file mode 100644 index 0000000..4c64667 --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/decode.go @@ -0,0 +1,119 @@ +package plist + +import ( + "bytes" + "io" + "reflect" + "runtime" +) + +type parser interface { + parseDocument() (cfValue, error) +} + +// A Decoder reads a property list from an input stream. +type Decoder struct { + // the format of the most-recently-decoded property list + Format int + + reader io.ReadSeeker + lax bool +} + +// Decode works like Unmarshal, except it reads the decoder stream to find property list elements. +// +// After Decoding, the Decoder's Format field will be set to one of the plist format constants. +func (p *Decoder) Decode(v interface{}) (err error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + err = r.(error) + } + }() + + header := make([]byte, 6) + p.reader.Read(header) + p.reader.Seek(0, 0) + + var parser parser + var pval cfValue + if bytes.Equal(header, []byte("bplist")) { + parser = newBplistParser(p.reader) + pval, err = parser.parseDocument() + if err != nil { + // Had a bplist header, but still got an error: we have to die here. + return err + } + p.Format = BinaryFormat + } else { + parser = newXMLPlistParser(p.reader) + pval, err = parser.parseDocument() + if _, ok := err.(invalidPlistError); ok { + // Rewind: the XML parser might have exhausted the file. + p.reader.Seek(0, 0) + // We don't use parser here because we want the textPlistParser type + tp := newTextPlistParser(p.reader) + pval, err = tp.parseDocument() + if err != nil { + return err + } + p.Format = tp.format + if p.Format == OpenStepFormat { + // OpenStep property lists can only store strings, + // so we have to turn on lax mode here for the unmarshal step later. + p.lax = true + } + } else { + if err != nil { + return err + } + p.Format = XMLFormat + } + } + + p.unmarshal(pval, reflect.ValueOf(v)) + return +} + +// NewDecoder returns a Decoder that reads property list elements from a stream reader, r. +// NewDecoder requires a Seekable stream for the purposes of file type detection. +func NewDecoder(r io.ReadSeeker) *Decoder { + return &Decoder{Format: InvalidFormat, reader: r, lax: false} +} + +// Unmarshal parses a property list document and stores the result in the value pointed to by v. +// +// Unmarshal uses the inverse of the type encodings that Marshal uses, allocating heap-borne types as necessary. +// +// When given a nil pointer, Unmarshal allocates a new value for it to point to. +// +// To decode property list values into an interface value, Unmarshal decodes the property list into the concrete value contained +// in the interface value. If the interface value is nil, Unmarshal stores one of the following in the interface value: +// +// string, bool, uint64, float64 +// plist.UID for "CoreFoundation Keyed Archiver UIDs" (convertible to uint64) +// []byte, for plist data +// []interface{}, for plist arrays +// map[string]interface{}, for plist dictionaries +// +// If a property list value is not appropriate for a given value type, Unmarshal aborts immediately and returns an error. +// +// As Go does not support 128-bit types, and we don't want to pretend we're giving the user integer types (as opposed to +// secretly passing them structs), Unmarshal will drop the high 64 bits of any 128-bit integers encoded in binary property lists. +// (This is important because CoreFoundation serializes some large 64-bit values as 128-bit values with an empty high half.) +// +// When Unmarshal encounters an OpenStep property list, it will enter a relaxed parsing mode: OpenStep property lists can only store +// plain old data as strings, so we will attempt to recover integer, floating-point, boolean and date values wherever they are necessary. +// (for example, if Unmarshal attempts to unmarshal an OpenStep property list into a time.Time, it will try to parse the string it +// receives as a time.) +// +// Unmarshal returns the detected property list format and an error, if any. +func Unmarshal(data []byte, v interface{}) (format int, err error) { + r := bytes.NewReader(data) + dec := NewDecoder(r) + err = dec.Decode(v) + format = dec.Format + return +} diff --git a/vendor/github.com/DHowett/go-plist/doc.go b/vendor/github.com/DHowett/go-plist/doc.go new file mode 100644 index 0000000..457e60b --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/doc.go @@ -0,0 +1,5 @@ +// Package plist implements encoding and decoding of Apple's "property list" format. +// Property lists come in three sorts: plain text (GNUStep and OpenStep), XML and binary. +// plist supports all of them. +// The mapping between property list and Go objects is described in the documentation for the Marshal and Unmarshal functions. +package plist diff --git a/vendor/github.com/DHowett/go-plist/encode.go b/vendor/github.com/DHowett/go-plist/encode.go new file mode 100644 index 0000000..f81309b --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/encode.go @@ -0,0 +1,126 @@ +package plist + +import ( + "bytes" + "errors" + "io" + "reflect" + "runtime" +) + +type generator interface { + generateDocument(cfValue) + Indent(string) +} + +// An Encoder writes a property list to an output stream. +type Encoder struct { + writer io.Writer + format int + + indent string +} + +// Encode writes the property list encoding of v to the stream. +func (p *Encoder) Encode(v interface{}) (err error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + err = r.(error) + } + }() + + pval := p.marshal(reflect.ValueOf(v)) + if pval == nil { + panic(errors.New("plist: no root element to encode")) + } + + var g generator + switch p.format { + case XMLFormat: + g = newXMLPlistGenerator(p.writer) + case BinaryFormat, AutomaticFormat: + g = newBplistGenerator(p.writer) + case OpenStepFormat, GNUStepFormat: + g = newTextPlistGenerator(p.writer, p.format) + } + g.Indent(p.indent) + g.generateDocument(pval) + return +} + +// Indent turns on pretty-printing for the XML and Text property list formats. +// Each element begins on a new line and is preceded by one or more copies of indent according to its nesting depth. +func (p *Encoder) Indent(indent string) { + p.indent = indent +} + +// NewEncoder returns an Encoder that writes an XML property list to w. +func NewEncoder(w io.Writer) *Encoder { + return NewEncoderForFormat(w, XMLFormat) +} + +// NewEncoderForFormat returns an Encoder that writes a property list to w in the specified format. +// Pass AutomaticFormat to allow the library to choose the best encoding (currently BinaryFormat). +func NewEncoderForFormat(w io.Writer, format int) *Encoder { + return &Encoder{ + writer: w, + format: format, + } +} + +// NewBinaryEncoder returns an Encoder that writes a binary property list to w. +func NewBinaryEncoder(w io.Writer) *Encoder { + return NewEncoderForFormat(w, BinaryFormat) +} + +// Marshal returns the property list encoding of v in the specified format. +// +// Pass AutomaticFormat to allow the library to choose the best encoding (currently BinaryFormat). +// +// Marshal traverses the value v recursively. +// Any nil values encountered, other than the root, will be silently discarded as +// the property list format bears no representation for nil values. +// +// Strings, integers of varying size, floats and booleans are encoded unchanged. +// Strings bearing non-ASCII runes will be encoded differently depending upon the property list format: +// UTF-8 for XML property lists and UTF-16 for binary property lists. +// +// Slice and Array values are encoded as property list arrays, except for +// []byte values, which are encoded as data. +// +// Map values encode as dictionaries. The map's key type must be string; there is no provision for encoding non-string dictionary keys. +// +// Struct values are encoded as dictionaries, with only exported fields being serialized. Struct field encoding may be influenced with the use of tags. +// The tag format is: +// +// `plist:"[,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 +} diff --git a/vendor/github.com/DHowett/go-plist/fuzz.go b/vendor/github.com/DHowett/go-plist/fuzz.go new file mode 100644 index 0000000..18a3b4b --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/fuzz.go @@ -0,0 +1,17 @@ +// +build gofuzz + +package plist + +import ( + "bytes" +) + +func Fuzz(data []byte) int { + buf := bytes.NewReader(data) + + var obj interface{} + if err := NewDecoder(buf).Decode(&obj); err != nil { + return 0 + } + return 1 +} diff --git a/vendor/github.com/DHowett/go-plist/marshal.go b/vendor/github.com/DHowett/go-plist/marshal.go new file mode 100644 index 0000000..c461dfb --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/marshal.go @@ -0,0 +1,186 @@ +package plist + +import ( + "encoding" + "reflect" + "time" +) + +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} + +var ( + plistMarshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() + textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() + timeType = reflect.TypeOf((*time.Time)(nil)).Elem() +) + +func implementsInterface(val reflect.Value, interfaceType reflect.Type) (interface{}, bool) { + if val.CanInterface() && val.Type().Implements(interfaceType) { + return val.Interface(), true + } + + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(interfaceType) { + return pv.Interface(), true + } + } + return nil, false +} + +func (p *Encoder) marshalPlistInterface(marshalable Marshaler) cfValue { + value, err := marshalable.MarshalPlist() + if err != nil { + panic(err) + } + return p.marshal(reflect.ValueOf(value)) +} + +// marshalTextInterface marshals a TextMarshaler to a plist string. +func (p *Encoder) marshalTextInterface(marshalable encoding.TextMarshaler) cfValue { + s, err := marshalable.MarshalText() + if err != nil { + panic(err) + } + return cfString(s) +} + +// marshalStruct marshals a reflected struct value to a plist dictionary +func (p *Encoder) marshalStruct(typ reflect.Type, val reflect.Value) cfValue { + tinfo, _ := getTypeInfo(typ) + + dict := &cfDictionary{ + keys: make([]string, 0, len(tinfo.fields)), + values: make([]cfValue, 0, len(tinfo.fields)), + } + for _, finfo := range tinfo.fields { + value := finfo.value(val) + if !value.IsValid() || finfo.omitEmpty && isEmptyValue(value) { + continue + } + dict.keys = append(dict.keys, finfo.name) + dict.values = append(dict.values, p.marshal(value)) + } + + return dict +} + +func (p *Encoder) marshalTime(val reflect.Value) cfValue { + time := val.Interface().(time.Time) + return cfDate(time) +} + +func (p *Encoder) marshal(val reflect.Value) cfValue { + if !val.IsValid() { + return nil + } + + if receiver, can := implementsInterface(val, plistMarshalerType); can { + return p.marshalPlistInterface(receiver.(Marshaler)) + } + + // time.Time implements TextMarshaler, but we need to store it in RFC3339 + if val.Type() == timeType { + return p.marshalTime(val) + } + if val.Kind() == reflect.Ptr || (val.Kind() == reflect.Interface && val.NumMethod() == 0) { + ival := val.Elem() + if ival.IsValid() && ival.Type() == timeType { + return p.marshalTime(ival) + } + } + + // Check for text marshaler. + if receiver, can := implementsInterface(val, textMarshalerType); can { + return p.marshalTextInterface(receiver.(encoding.TextMarshaler)) + } + + // Descend into pointers or interfaces + if val.Kind() == reflect.Ptr || (val.Kind() == reflect.Interface && val.NumMethod() == 0) { + val = val.Elem() + } + + // We got this far and still may have an invalid anything or nil ptr/interface + if !val.IsValid() || ((val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface) && val.IsNil()) { + return nil + } + + typ := val.Type() + + if typ == uidType { + return cfUID(val.Uint()) + } + + if val.Kind() == reflect.Struct { + return p.marshalStruct(typ, val) + } + + switch val.Kind() { + case reflect.String: + return cfString(val.String()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return &cfNumber{signed: true, value: uint64(val.Int())} + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return &cfNumber{signed: false, value: val.Uint()} + case reflect.Float32: + return &cfReal{wide: false, value: val.Float()} + case reflect.Float64: + return &cfReal{wide: true, value: val.Float()} + case reflect.Bool: + return cfBoolean(val.Bool()) + case reflect.Slice, reflect.Array: + if typ.Elem().Kind() == reflect.Uint8 { + bytes := []byte(nil) + if val.CanAddr() { + bytes = val.Bytes() + } else { + bytes = make([]byte, val.Len()) + reflect.Copy(reflect.ValueOf(bytes), val) + } + return cfData(bytes) + } else { + values := make([]cfValue, val.Len()) + for i, length := 0, val.Len(); i < length; i++ { + if subpval := p.marshal(val.Index(i)); subpval != nil { + values[i] = subpval + } + } + return &cfArray{values} + } + case reflect.Map: + if typ.Key().Kind() != reflect.String { + panic(&unknownTypeError{typ}) + } + + l := val.Len() + dict := &cfDictionary{ + keys: make([]string, 0, l), + values: make([]cfValue, 0, l), + } + for _, keyv := range val.MapKeys() { + if subpval := p.marshal(val.MapIndex(keyv)); subpval != nil { + dict.keys = append(dict.keys, keyv.String()) + dict.values = append(dict.values, subpval) + } + } + return dict + default: + panic(&unknownTypeError{typ}) + } +} diff --git a/vendor/github.com/DHowett/go-plist/must.go b/vendor/github.com/DHowett/go-plist/must.go new file mode 100644 index 0000000..2c2523d --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/must.go @@ -0,0 +1,50 @@ +package plist + +import ( + "io" + "strconv" +) + +type mustWriter struct { + io.Writer +} + +func (w mustWriter) Write(p []byte) (int, error) { + n, err := w.Writer.Write(p) + if err != nil { + panic(err) + } + return n, nil +} + +func mustParseInt(str string, base, bits int) int64 { + i, err := strconv.ParseInt(str, base, bits) + if err != nil { + panic(err) + } + return i +} + +func mustParseUint(str string, base, bits int) uint64 { + i, err := strconv.ParseUint(str, base, bits) + if err != nil { + panic(err) + } + return i +} + +func mustParseFloat(str string, bits int) float64 { + i, err := strconv.ParseFloat(str, bits) + if err != nil { + panic(err) + } + return i +} + +func mustParseBool(str string) bool { + i, err := strconv.ParseBool(str) + if err != nil { + panic(err) + } + return i +} diff --git a/vendor/github.com/DHowett/go-plist/plist.go b/vendor/github.com/DHowett/go-plist/plist.go new file mode 100644 index 0000000..a4078b2 --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/plist.go @@ -0,0 +1,85 @@ +package plist + +import ( + "reflect" +) + +// Property list format constants +const ( + // Used by Decoder to represent an invalid property list. + InvalidFormat int = 0 + + // Used to indicate total abandon with regards to Encoder's output format. + AutomaticFormat = 0 + + XMLFormat = 1 + BinaryFormat = 2 + OpenStepFormat = 3 + GNUStepFormat = 4 +) + +var FormatNames = map[int]string{ + InvalidFormat: "unknown/invalid", + XMLFormat: "XML", + BinaryFormat: "Binary", + OpenStepFormat: "OpenStep", + GNUStepFormat: "GNUStep", +} + +type unknownTypeError struct { + typ reflect.Type +} + +func (u *unknownTypeError) Error() string { + return "plist: can't marshal value of type " + u.typ.String() +} + +type invalidPlistError struct { + format string + err error +} + +func (e invalidPlistError) Error() string { + s := "plist: invalid " + e.format + " property list" + if e.err != nil { + s += ": " + e.err.Error() + } + return s +} + +type plistParseError struct { + format string + err error +} + +func (e plistParseError) Error() string { + s := "plist: error parsing " + e.format + " property list" + if e.err != nil { + s += ": " + e.err.Error() + } + return s +} + +// A UID represents a unique object identifier. UIDs are serialized in a manner distinct from +// that of integers. +// +// UIDs cannot be serialized in OpenStepFormat or GNUStepFormat property lists. +type UID uint64 + +// Marshaler is the interface implemented by types that can marshal themselves into valid +// property list objects. The returned value is marshaled in place of the original value +// implementing Marshaler +// +// If an error is returned by MarshalPlist, marshaling stops and the error is returned. +type Marshaler interface { + MarshalPlist() (interface{}, error) +} + +// Unmarshaler is the interface implemented by types that can unmarshal themselves from +// property list objects. The UnmarshalPlist method receives a function that may +// be called to unmarshal the original property list value into a field or variable. +// +// It is safe to call the unmarshal function more than once. +type Unmarshaler interface { + UnmarshalPlist(unmarshal func(interface{}) error) error +} diff --git a/vendor/github.com/DHowett/go-plist/plist_types.go b/vendor/github.com/DHowett/go-plist/plist_types.go new file mode 100644 index 0000000..954ef34 --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/plist_types.go @@ -0,0 +1,139 @@ +package plist + +import ( + "hash/crc32" + "sort" + "time" +) + +type cfValue interface { + typeName() string + hash() interface{} +} + +type cfDictionary struct { + keys sort.StringSlice + values []cfValue +} + +func (*cfDictionary) typeName() string { + return "dictionary" +} + +func (p *cfDictionary) hash() interface{} { + return p +} + +func (p *cfDictionary) Len() int { + return len(p.keys) +} + +func (p *cfDictionary) Less(i, j int) bool { + return p.keys.Less(i, j) +} + +func (p *cfDictionary) Swap(i, j int) { + p.keys.Swap(i, j) + p.values[i], p.values[j] = p.values[j], p.values[i] +} + +func (p *cfDictionary) sort() { + sort.Sort(p) +} + +type cfArray struct { + values []cfValue +} + +func (*cfArray) typeName() string { + return "array" +} + +func (p *cfArray) hash() interface{} { + return p +} + +type cfString string + +func (cfString) typeName() string { + return "string" +} + +func (p cfString) hash() interface{} { + return string(p) +} + +type cfNumber struct { + signed bool + value uint64 +} + +func (*cfNumber) typeName() string { + return "integer" +} + +func (p *cfNumber) hash() interface{} { + if p.signed { + return int64(p.value) + } + return p.value +} + +type cfReal struct { + wide bool + value float64 +} + +func (cfReal) typeName() string { + return "real" +} + +func (p *cfReal) hash() interface{} { + if p.wide { + return p.value + } + return float32(p.value) +} + +type cfBoolean bool + +func (cfBoolean) typeName() string { + return "boolean" +} + +func (p cfBoolean) hash() interface{} { + return bool(p) +} + +type cfUID UID + +func (cfUID) typeName() string { + return "UID" +} + +func (p cfUID) hash() interface{} { + return p +} + +type cfData []byte + +func (cfData) typeName() string { + return "data" +} + +func (p cfData) hash() interface{} { + // Data are uniqued by their checksums. + // Todo: Look at calculating this only once and storing it somewhere; + // crc32 is fairly quick, however. + return crc32.ChecksumIEEE([]byte(p)) +} + +type cfDate time.Time + +func (cfDate) typeName() string { + return "date" +} + +func (p cfDate) hash() interface{} { + return time.Time(p) +} diff --git a/vendor/github.com/DHowett/go-plist/text_generator.go b/vendor/github.com/DHowett/go-plist/text_generator.go new file mode 100644 index 0000000..53078ba --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/text_generator.go @@ -0,0 +1,226 @@ +package plist + +import ( + "encoding/hex" + "io" + "strconv" + "time" +) + +type textPlistGenerator struct { + writer io.Writer + format int + + quotableTable *characterSet + + indent string + depth int + + dictKvDelimiter, dictEntryDelimiter, arrayDelimiter []byte +} + +var ( + textPlistTimeLayout = "2006-01-02 15:04:05 -0700" + padding = "0000" +) + +func (p *textPlistGenerator) generateDocument(pval cfValue) { + p.writePlistValue(pval) +} + +func (p *textPlistGenerator) plistQuotedString(str string) string { + if str == "" { + return `""` + } + s := "" + quot := false + for _, r := range str { + if r > 0xFF { + quot = true + s += `\U` + us := strconv.FormatInt(int64(r), 16) + s += padding[len(us):] + s += us + } else if r > 0x7F { + quot = true + s += `\` + us := strconv.FormatInt(int64(r), 8) + s += padding[1+len(us):] + s += us + } else { + c := uint8(r) + if p.quotableTable.ContainsByte(c) { + quot = true + } + + switch c { + case '\a': + s += `\a` + case '\b': + s += `\b` + case '\v': + s += `\v` + case '\f': + s += `\f` + case '\\': + s += `\\` + case '"': + s += `\"` + case '\t', '\r', '\n': + fallthrough + default: + s += string(c) + } + } + } + if quot { + s = `"` + s + `"` + } + return s +} + +func (p *textPlistGenerator) deltaIndent(depthDelta int) { + if depthDelta < 0 { + p.depth-- + } else if depthDelta > 0 { + p.depth++ + } +} + +func (p *textPlistGenerator) writeIndent() { + if len(p.indent) == 0 { + return + } + if len(p.indent) > 0 { + p.writer.Write([]byte("\n")) + for i := 0; i < p.depth; i++ { + io.WriteString(p.writer, p.indent) + } + } +} + +func (p *textPlistGenerator) writePlistValue(pval cfValue) { + if pval == nil { + return + } + + switch pval := pval.(type) { + case *cfDictionary: + pval.sort() + p.writer.Write([]byte(`{`)) + p.deltaIndent(1) + for i, k := range pval.keys { + p.writeIndent() + io.WriteString(p.writer, p.plistQuotedString(k)) + p.writer.Write(p.dictKvDelimiter) + p.writePlistValue(pval.values[i]) + p.writer.Write(p.dictEntryDelimiter) + } + p.deltaIndent(-1) + p.writeIndent() + p.writer.Write([]byte(`}`)) + case *cfArray: + p.writer.Write([]byte(`(`)) + p.deltaIndent(1) + for _, v := range pval.values { + p.writeIndent() + p.writePlistValue(v) + p.writer.Write(p.arrayDelimiter) + } + p.deltaIndent(-1) + p.writeIndent() + p.writer.Write([]byte(`)`)) + case cfString: + io.WriteString(p.writer, p.plistQuotedString(string(pval))) + case *cfNumber: + if p.format == GNUStepFormat { + p.writer.Write([]byte(`<*I`)) + } + if pval.signed { + io.WriteString(p.writer, strconv.FormatInt(int64(pval.value), 10)) + } else { + io.WriteString(p.writer, strconv.FormatUint(pval.value, 10)) + } + if p.format == GNUStepFormat { + p.writer.Write([]byte(`>`)) + } + case *cfReal: + if p.format == GNUStepFormat { + p.writer.Write([]byte(`<*R`)) + } + // GNUstep does not differentiate between 32/64-bit floats. + io.WriteString(p.writer, strconv.FormatFloat(pval.value, 'g', -1, 64)) + if p.format == GNUStepFormat { + p.writer.Write([]byte(`>`)) + } + case cfBoolean: + if p.format == GNUStepFormat { + if pval { + p.writer.Write([]byte(`<*BY>`)) + } else { + p.writer.Write([]byte(`<*BN>`)) + } + } else { + if pval { + p.writer.Write([]byte(`1`)) + } else { + p.writer.Write([]byte(`0`)) + } + } + case cfData: + var hexencoded [9]byte + var l int + var asc = 9 + hexencoded[8] = ' ' + + p.writer.Write([]byte(`<`)) + b := []byte(pval) + for i := 0; i < len(b); i += 4 { + l = i + 4 + if l >= len(b) { + l = len(b) + // We no longer need the space - or the rest of the buffer. + // (we used >= above to get this part without another conditional :P) + asc = (l - i) * 2 + } + // Fill the buffer (only up to 8 characters, to preserve the space we implicitly include + // at the end of every encode) + hex.Encode(hexencoded[:8], b[i:l]) + io.WriteString(p.writer, string(hexencoded[:asc])) + } + p.writer.Write([]byte(`>`)) + case cfDate: + if p.format == GNUStepFormat { + p.writer.Write([]byte(`<*D`)) + io.WriteString(p.writer, time.Time(pval).In(time.UTC).Format(textPlistTimeLayout)) + p.writer.Write([]byte(`>`)) + } else { + io.WriteString(p.writer, p.plistQuotedString(time.Time(pval).In(time.UTC).Format(textPlistTimeLayout))) + } + } +} + +func (p *textPlistGenerator) Indent(i string) { + p.indent = i + if i == "" { + p.dictKvDelimiter = []byte(`=`) + } else { + // For pretty-printing + p.dictKvDelimiter = []byte(` = `) + } +} + +func newTextPlistGenerator(w io.Writer, format int) *textPlistGenerator { + table := &osQuotable + if format == GNUStepFormat { + table = &gsQuotable + } + return &textPlistGenerator{ + writer: mustWriter{w}, + format: format, + quotableTable: table, + dictKvDelimiter: []byte(`=`), + arrayDelimiter: []byte(`,`), + dictEntryDelimiter: []byte(`;`), + } +} diff --git a/vendor/github.com/DHowett/go-plist/text_parser.go b/vendor/github.com/DHowett/go-plist/text_parser.go new file mode 100644 index 0000000..7e49d6f --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/text_parser.go @@ -0,0 +1,515 @@ +package plist + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "runtime" + "strings" + "time" + "unicode/utf16" + "unicode/utf8" +) + +type textPlistParser struct { + reader io.Reader + format int + + input string + start int + pos int + width int +} + +func convertU16(buffer []byte, bo binary.ByteOrder) (string, error) { + if len(buffer)%2 != 0 { + return "", errors.New("truncated utf16") + } + + tmp := make([]uint16, len(buffer)/2) + for i := 0; i < len(buffer); i += 2 { + tmp[i/2] = bo.Uint16(buffer[i : i+2]) + } + return string(utf16.Decode(tmp)), nil +} + +func guessEncodingAndConvert(buffer []byte) (string, error) { + if len(buffer) >= 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF { + // UTF-8 BOM + return zeroCopy8BitString(buffer, 3, len(buffer)-3), nil + } else if len(buffer) >= 2 { + // UTF-16 guesses + + switch { + // stream is big-endian (BOM is FE FF or head is 00 XX) + case (buffer[0] == 0xFE && buffer[1] == 0xFF): + return convertU16(buffer[2:], binary.BigEndian) + case (buffer[0] == 0 && buffer[1] != 0): + return convertU16(buffer, binary.BigEndian) + + // stream is little-endian (BOM is FE FF or head is XX 00) + case (buffer[0] == 0xFF && buffer[1] == 0xFE): + return convertU16(buffer[2:], binary.LittleEndian) + case (buffer[0] != 0 && buffer[1] == 0): + return convertU16(buffer, binary.LittleEndian) + } + } + + // fallback: assume ASCII (not great!) + return zeroCopy8BitString(buffer, 0, len(buffer)), nil +} + +func (p *textPlistParser) parseDocument() (pval cfValue, parseError error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + // Wrap all non-invalid-plist errors. + parseError = plistParseError{"text", r.(error)} + } + }() + + buffer, err := ioutil.ReadAll(p.reader) + if err != nil { + panic(err) + } + + p.input, err = guessEncodingAndConvert(buffer) + if err != nil { + panic(err) + } + + val := p.parsePlistValue() + + p.skipWhitespaceAndComments() + if p.peek() != eof { + if _, ok := val.(cfString); !ok { + p.error("garbage after end of document") + } + + p.start = 0 + p.pos = 0 + val = p.parseDictionary(true) + } + + pval = val + + return +} + +const eof rune = -1 + +func (p *textPlistParser) error(e string, args ...interface{}) { + line := strings.Count(p.input[:p.pos], "\n") + char := p.pos - strings.LastIndex(p.input[:p.pos], "\n") - 1 + panic(fmt.Errorf("%s at line %d character %d", fmt.Sprintf(e, args...), line, char)) +} + +func (p *textPlistParser) next() rune { + if int(p.pos) >= len(p.input) { + p.width = 0 + return eof + } + r, w := utf8.DecodeRuneInString(p.input[p.pos:]) + p.width = w + p.pos += p.width + return r +} + +func (p *textPlistParser) backup() { + p.pos -= p.width +} + +func (p *textPlistParser) peek() rune { + r := p.next() + p.backup() + return r +} + +func (p *textPlistParser) emit() string { + s := p.input[p.start:p.pos] + p.start = p.pos + return s +} + +func (p *textPlistParser) ignore() { + p.start = p.pos +} + +func (p *textPlistParser) empty() bool { + return p.start == p.pos +} + +func (p *textPlistParser) scanUntil(ch rune) { + if x := strings.IndexRune(p.input[p.pos:], ch); x >= 0 { + p.pos += x + return + } + p.pos = len(p.input) +} + +func (p *textPlistParser) scanUntilAny(chs string) { + if x := strings.IndexAny(p.input[p.pos:], chs); x >= 0 { + p.pos += x + return + } + p.pos = len(p.input) +} + +func (p *textPlistParser) scanCharactersInSet(ch *characterSet) { + for ch.Contains(p.next()) { + } + p.backup() +} + +func (p *textPlistParser) scanCharactersNotInSet(ch *characterSet) { + var r rune + for { + r = p.next() + if r == eof || ch.Contains(r) { + break + } + } + p.backup() +} + +func (p *textPlistParser) skipWhitespaceAndComments() { + for { + p.scanCharactersInSet(&whitespace) + if strings.HasPrefix(p.input[p.pos:], "//") { + p.scanCharactersNotInSet(&newlineCharacterSet) + } else if strings.HasPrefix(p.input[p.pos:], "/*") { + if x := strings.Index(p.input[p.pos:], "*/"); x >= 0 { + p.pos += x + 2 // skip the */ as well + continue // consume more whitespace + } else { + p.error("unexpected eof in block comment") + } + } else { + break + } + } + p.ignore() +} + +func (p *textPlistParser) parseOctalDigits(max int) uint64 { + var val uint64 + + for i := 0; i < max; i++ { + r := p.next() + + if r >= '0' && r <= '7' { + val <<= 3 + val |= uint64((r - '0')) + } else { + p.backup() + break + } + } + return val +} + +func (p *textPlistParser) parseHexDigits(max int) uint64 { + var val uint64 + + for i := 0; i < max; i++ { + r := p.next() + + if r >= 'a' && r <= 'f' { + val <<= 4 + val |= 10 + uint64((r - 'a')) + } else if r >= 'A' && r <= 'F' { + val <<= 4 + val |= 10 + uint64((r - 'A')) + } else if r >= '0' && r <= '9' { + val <<= 4 + val |= uint64((r - '0')) + } else { + p.backup() + break + } + } + return val +} + +// the \ has already been consumed +func (p *textPlistParser) parseEscape() string { + var s string + switch p.next() { + case 'a': + s = "\a" + case 'b': + s = "\b" + case 'v': + s = "\v" + case 'f': + s = "\f" + case 't': + s = "\t" + case 'r': + s = "\r" + case 'n': + s = "\n" + case '\\': + s = `\` + case '"': + s = `"` + case 'x': + s = string(rune(p.parseHexDigits(2))) + case 'u', 'U': + s = string(rune(p.parseHexDigits(4))) + case '0', '1', '2', '3', '4', '5', '6', '7': + p.backup() // we've already consumed one of the digits + s = string(rune(p.parseOctalDigits(3))) + default: + p.backup() // everything else should be accepted + } + p.ignore() // skip the entire escape sequence + return s +} + +// the " has already been consumed +func (p *textPlistParser) parseQuotedString() cfString { + p.ignore() // ignore the " + + slowPath := false + s := "" + + for { + p.scanUntilAny(`"\`) + switch p.peek() { + case eof: + p.error("unexpected eof in quoted string") + case '"': + section := p.emit() + p.pos++ // skip " + if !slowPath { + return cfString(section) + } else { + s += section + return cfString(s) + } + case '\\': + slowPath = true + s += p.emit() + p.next() // consume \ + s += p.parseEscape() + } + } +} + +func (p *textPlistParser) parseUnquotedString() cfString { + p.scanCharactersNotInSet(&gsQuotable) + s := p.emit() + if s == "" { + p.error("invalid unquoted string (found an unquoted character that should be quoted?)") + } + + return cfString(s) +} + +// the { has already been consumed +func (p *textPlistParser) parseDictionary(ignoreEof bool) *cfDictionary { + //p.ignore() // ignore the { + var keypv cfValue + keys := make([]string, 0, 32) + values := make([]cfValue, 0, 32) +outer: + for { + p.skipWhitespaceAndComments() + + switch p.next() { + case eof: + if !ignoreEof { + p.error("unexpected eof in dictionary") + } + fallthrough + case '}': + break outer + case '"': + keypv = p.parseQuotedString() + default: + p.backup() + keypv = p.parseUnquotedString() + } + + // INVARIANT: key can't be nil; parseQuoted and parseUnquoted + // will panic out before they return nil. + + p.skipWhitespaceAndComments() + + var val cfValue + n := p.next() + if n == ';' { + val = keypv + } else if n == '=' { + // whitespace is consumed within + val = p.parsePlistValue() + + p.skipWhitespaceAndComments() + + if p.next() != ';' { + p.error("missing ; in dictionary") + } + } else { + p.error("missing = in dictionary") + } + + keys = append(keys, string(keypv.(cfString))) + values = append(values, val) + } + + return &cfDictionary{keys: keys, values: values} +} + +// the ( has already been consumed +func (p *textPlistParser) parseArray() *cfArray { + //p.ignore() // ignore the ( + values := make([]cfValue, 0, 32) +outer: + for { + p.skipWhitespaceAndComments() + + switch p.next() { + case eof: + p.error("unexpected eof in array") + case ')': + break outer // done here + case ',': + continue // restart; ,) is valid and we don't want to blow it + default: + p.backup() + } + + pval := p.parsePlistValue() // whitespace is consumed within + if str, ok := pval.(cfString); ok && string(str) == "" { + // Empty strings in arrays are apparently skipped? + // TODO: Figure out why this was implemented. + continue + } + values = append(values, pval) + } + return &cfArray{values} +} + +// the <* have already been consumed +func (p *textPlistParser) parseGNUStepValue() cfValue { + typ := p.next() + p.ignore() + p.scanUntil('>') + + if typ == eof || typ == '>' || p.empty() || p.peek() == eof { + p.error("invalid GNUStep extended value") + } + + v := p.emit() + p.next() // consume the > + + switch typ { + case 'I': + if v[0] == '-' { + n := mustParseInt(v, 10, 64) + return &cfNumber{signed: true, value: uint64(n)} + } else { + n := mustParseUint(v, 10, 64) + return &cfNumber{signed: false, value: n} + } + case 'R': + n := mustParseFloat(v, 64) + return &cfReal{wide: true, value: n} // TODO(DH) 32/64 + case 'B': + b := v[0] == 'Y' + return cfBoolean(b) + case 'D': + t, err := time.Parse(textPlistTimeLayout, v) + if err != nil { + p.error(err.Error()) + } + + return cfDate(t.In(time.UTC)) + } + p.error("invalid GNUStep type " + string(typ)) + return nil +} + +// The < has already been consumed +func (p *textPlistParser) parseHexData() cfData { + buf := make([]byte, 256) + i := 0 + c := 0 + + for { + r := p.next() + switch r { + case eof: + p.error("unexpected eof in data") + case '>': + if c&1 == 1 { + p.error("uneven number of hex digits in data") + } + p.ignore() + return cfData(buf[:i]) + case ' ', '\t', '\n', '\r', '\u2028', '\u2029': // more lax than apple here: skip spaces + continue + } + + buf[i] <<= 4 + if r >= 'a' && r <= 'f' { + buf[i] |= 10 + byte((r - 'a')) + } else if r >= 'A' && r <= 'F' { + buf[i] |= 10 + byte((r - 'A')) + } else if r >= '0' && r <= '9' { + buf[i] |= byte((r - '0')) + } else { + p.error("unexpected hex digit `%c'", r) + } + + c++ + if c&1 == 0 { + i++ + if i >= len(buf) { + realloc := make([]byte, len(buf)*2) + copy(realloc, buf) + buf = realloc + } + } + } +} + +func (p *textPlistParser) parsePlistValue() cfValue { + for { + p.skipWhitespaceAndComments() + + switch p.next() { + case eof: + return &cfDictionary{} + case '<': + if p.next() == '*' { + p.format = GNUStepFormat + return p.parseGNUStepValue() + } + + p.backup() + return p.parseHexData() + case '"': + return p.parseQuotedString() + case '{': + return p.parseDictionary(false) + case '(': + return p.parseArray() + default: + p.backup() + return p.parseUnquotedString() + } + } +} + +func newTextPlistParser(r io.Reader) *textPlistParser { + return &textPlistParser{ + reader: r, + format: OpenStepFormat, + } +} diff --git a/vendor/github.com/DHowett/go-plist/text_tables.go b/vendor/github.com/DHowett/go-plist/text_tables.go new file mode 100644 index 0000000..319c55c --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/text_tables.go @@ -0,0 +1,43 @@ +package plist + +type characterSet [4]uint64 + +func (s *characterSet) Contains(ch rune) bool { + return ch >= 0 && ch <= 255 && s.ContainsByte(byte(ch)) +} + +func (s *characterSet) ContainsByte(ch byte) bool { + return (s[ch/64]&(1<<(ch%64)) > 0) +} + +// Bitmap of characters that must be inside a quoted string +// when written to an old-style property list +// Low bits represent lower characters, and each uint64 represents 64 characters. +var gsQuotable = characterSet{ + 0x78001385ffffffff, + 0xa800000138000000, + 0xffffffffffffffff, + 0xffffffffffffffff, +} + +// 7f instead of 3f in the top line: CFOldStylePlist.c says . is valid, but they quote it. +var osQuotable = characterSet{ + 0xf4007f6fffffffff, + 0xf8000001f8000001, + 0xffffffffffffffff, + 0xffffffffffffffff, +} + +var whitespace = characterSet{ + 0x0000000100003f00, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, +} + +var newlineCharacterSet = characterSet{ + 0x0000000000002400, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, +} diff --git a/vendor/github.com/DHowett/go-plist/typeinfo.go b/vendor/github.com/DHowett/go-plist/typeinfo.go new file mode 100644 index 0000000..f0b920f --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/typeinfo.go @@ -0,0 +1,170 @@ +package plist + +import ( + "reflect" + "strings" + "sync" +) + +// typeInfo holds details for the plist representation of a type. +type typeInfo struct { + fields []fieldInfo +} + +// fieldInfo holds details for the plist representation of a single field. +type fieldInfo struct { + idx []int + name string + omitEmpty bool +} + +var tinfoMap = make(map[reflect.Type]*typeInfo) +var tinfoLock sync.RWMutex + +// getTypeInfo returns the typeInfo structure with details necessary +// for marshalling and unmarshalling typ. +func getTypeInfo(typ reflect.Type) (*typeInfo, error) { + tinfoLock.RLock() + tinfo, ok := tinfoMap[typ] + tinfoLock.RUnlock() + if ok { + return tinfo, nil + } + tinfo = &typeInfo{} + if typ.Kind() == reflect.Struct { + n := typ.NumField() + for i := 0; i < n; i++ { + f := typ.Field(i) + if f.PkgPath != "" || f.Tag.Get("plist") == "-" { + continue // Private field + } + + // For embedded structs, embed its fields. + if f.Anonymous { + t := f.Type + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() == reflect.Struct { + inner, err := getTypeInfo(t) + if err != nil { + return nil, err + } + for _, finfo := range inner.fields { + finfo.idx = append([]int{i}, finfo.idx...) + if err := addFieldInfo(typ, tinfo, &finfo); err != nil { + return nil, err + } + } + continue + } + } + + finfo, err := structFieldInfo(typ, &f) + if err != nil { + return nil, err + } + + // Add the field if it doesn't conflict with other fields. + if err := addFieldInfo(typ, tinfo, finfo); err != nil { + return nil, err + } + } + } + tinfoLock.Lock() + tinfoMap[typ] = tinfo + tinfoLock.Unlock() + return tinfo, nil +} + +// structFieldInfo builds and returns a fieldInfo for f. +func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) { + finfo := &fieldInfo{idx: f.Index} + + // Split the tag from the xml namespace if necessary. + tag := f.Tag.Get("plist") + + // Parse flags. + tokens := strings.Split(tag, ",") + tag = tokens[0] + if len(tokens) > 1 { + tag = tokens[0] + for _, flag := range tokens[1:] { + switch flag { + case "omitempty": + finfo.omitEmpty = true + } + } + } + + if tag == "" { + // If the name part of the tag is completely empty, + // use the field name + finfo.name = f.Name + return finfo, nil + } + + finfo.name = tag + return finfo, nil +} + +// addFieldInfo adds finfo to tinfo.fields if there are no +// conflicts, or if conflicts arise from previous fields that were +// obtained from deeper embedded structures than finfo. In the latter +// case, the conflicting entries are dropped. +// A conflict occurs when the path (parent + name) to a field is +// itself a prefix of another path, or when two paths match exactly. +// It is okay for field paths to share a common, shorter prefix. +func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error { + var conflicts []int + // First, figure all conflicts. Most working code will have none. + for i := range tinfo.fields { + oldf := &tinfo.fields[i] + if newf.name == oldf.name { + conflicts = append(conflicts, i) + } + } + + // Without conflicts, add the new field and return. + if conflicts == nil { + tinfo.fields = append(tinfo.fields, *newf) + return nil + } + + // If any conflict is shallower, ignore the new field. + // This matches the Go field resolution on embedding. + for _, i := range conflicts { + if len(tinfo.fields[i].idx) < len(newf.idx) { + return nil + } + } + + // Otherwise, the new field is shallower, and thus takes precedence, + // so drop the conflicting fields from tinfo and append the new one. + for c := len(conflicts) - 1; c >= 0; c-- { + i := conflicts[c] + copy(tinfo.fields[i:], tinfo.fields[i+1:]) + tinfo.fields = tinfo.fields[:len(tinfo.fields)-1] + } + tinfo.fields = append(tinfo.fields, *newf) + return nil +} + +// value returns v's field value corresponding to finfo. +// It's equivalent to v.FieldByIndex(finfo.idx), but initializes +// and dereferences pointers as necessary. +func (finfo *fieldInfo) value(v reflect.Value) reflect.Value { + for i, x := range finfo.idx { + if i > 0 { + t := v.Type() + if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + } + v = v.Field(x) + } + return v +} diff --git a/vendor/github.com/DHowett/go-plist/unmarshal.go b/vendor/github.com/DHowett/go-plist/unmarshal.go new file mode 100644 index 0000000..e38cbe5 --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/unmarshal.go @@ -0,0 +1,320 @@ +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 := val.MapIndex(keyv) + if !mapElem.IsValid() { + 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 +} diff --git a/vendor/github.com/DHowett/go-plist/util.go b/vendor/github.com/DHowett/go-plist/util.go new file mode 100644 index 0000000..d4e437a --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/util.go @@ -0,0 +1,25 @@ +package plist + +import "io" + +type countedWriter struct { + io.Writer + nbytes int +} + +func (w *countedWriter) Write(p []byte) (int, error) { + n, err := w.Writer.Write(p) + w.nbytes += n + return n, err +} + +func (w *countedWriter) BytesWritten() int { + return w.nbytes +} + +func unsignedGetBase(s string) (string, int) { + if len(s) > 1 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') { + return s[2:], 16 + } + return s, 10 +} diff --git a/vendor/github.com/DHowett/go-plist/xml_generator.go b/vendor/github.com/DHowett/go-plist/xml_generator.go new file mode 100644 index 0000000..0b59ed7 --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/xml_generator.go @@ -0,0 +1,185 @@ +package plist + +import ( + "bufio" + "encoding/base64" + "encoding/xml" + "io" + "math" + "strconv" + "time" +) + +const ( + xmlHEADER string = `` + "\n" + xmlDOCTYPE = `` + "\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("') +} + +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("') + } +} + +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)} +} diff --git a/vendor/github.com/DHowett/go-plist/xml_parser.go b/vendor/github.com/DHowett/go-plist/xml_parser.go new file mode 100644 index 0000000..8d8cfd1 --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/xml_parser.go @@ -0,0 +1,216 @@ +package plist + +import ( + "encoding/base64" + "encoding/xml" + "errors" + "fmt" + "io" + "runtime" + "strings" + "time" +) + +type xmlPlistParser struct { + reader io.Reader + xmlDecoder *xml.Decoder + whitespaceReplacer *strings.Replacer + ntags int +} + +func (p *xmlPlistParser) parseDocument() (pval cfValue, parseError error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + if _, ok := r.(invalidPlistError); ok { + parseError = r.(error) + } else { + // Wrap all non-invalid-plist errors. + parseError = plistParseError{"XML", r.(error)} + } + } + }() + for { + if token, err := p.xmlDecoder.Token(); err == nil { + if element, ok := token.(xml.StartElement); ok { + pval = p.parseXMLElement(element) + if p.ntags == 0 { + panic(invalidPlistError{"XML", errors.New("no elements encountered")}) + } + return + } + } else { + // The first XML parse turned out to be invalid: + // we do not have an XML property list. + panic(invalidPlistError{"XML", err}) + } + } +} + +func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) cfValue { + var charData xml.CharData + switch element.Name.Local { + case "plist": + p.ntags++ + for { + token, err := p.xmlDecoder.Token() + if err != nil { + panic(err) + } + + if el, ok := token.(xml.EndElement); ok && el.Name.Local == "plist" { + break + } + + if el, ok := token.(xml.StartElement); ok { + return p.parseXMLElement(el) + } + } + return nil + case "string": + p.ntags++ + err := p.xmlDecoder.DecodeElement(&charData, &element) + if err != nil { + panic(err) + } + + return cfString(charData) + case "integer": + p.ntags++ + err := p.xmlDecoder.DecodeElement(&charData, &element) + if err != nil { + panic(err) + } + + s := string(charData) + if len(s) == 0 { + panic(errors.New("invalid empty ")) + } + + 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 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} +} diff --git a/vendor/github.com/DHowett/go-plist/zerocopy.go b/vendor/github.com/DHowett/go-plist/zerocopy.go new file mode 100644 index 0000000..999f401 --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/zerocopy.go @@ -0,0 +1,20 @@ +// +build !appengine + +package plist + +import ( + "reflect" + "unsafe" +) + +func zeroCopy8BitString(buf []byte, off int, len int) string { + if len == 0 { + return "" + } + + var s string + hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) + hdr.Data = uintptr(unsafe.Pointer(&buf[off])) + hdr.Len = len + return s +} diff --git a/vendor/github.com/DHowett/go-plist/zerocopy_appengine.go b/vendor/github.com/DHowett/go-plist/zerocopy_appengine.go new file mode 100644 index 0000000..dbd9a1a --- /dev/null +++ b/vendor/github.com/DHowett/go-plist/zerocopy_appengine.go @@ -0,0 +1,7 @@ +// +build appengine + +package plist + +func zeroCopy8BitString(buf []byte, off int, len int) string { + return string(buf[off : off+len]) +} diff --git a/vendor/github.com/andrianbdn/iospng/LICENSE b/vendor/github.com/andrianbdn/iospng/LICENSE new file mode 100644 index 0000000..986aaaf --- /dev/null +++ b/vendor/github.com/andrianbdn/iospng/LICENSE @@ -0,0 +1,22 @@ + +The MIT License (MIT) + +Copyright (c) 2017 Andrian Budantsov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/andrianbdn/iospng/README.md b/vendor/github.com/andrianbdn/iospng/README.md new file mode 100644 index 0000000..9718f1f --- /dev/null +++ b/vendor/github.com/andrianbdn/iospng/README.md @@ -0,0 +1,36 @@ +# iOS PNG File Normalizer + +This package reverts optimizations that are done by Xcode for PNG files when packaging iOS apps: + +- Removes CgBI chunks +- Fixes compressed IDAT chunks +- removes alpha pre-multiply + +The package does similar things like ipin.py or xcrun -sdk iphoneos pngcrush -revert-iphone-optimizations + +## Installation + + +The import path for the package is *github.com/andrianbdn/iospng*. + +To install it, run: + + go get github.com/andrianbdn/iospng + + +## Usage + +#### func PngRevertOptimization + + +```go +func PngRevertOptimization(reader io.Reader, writer io.Writer) error +``` + +This function actually does everything: reads PNG from reader and in case it is iOS-optimized, reverts optimization. +Function does not change data if PNG does not have CgBI chunk. + + +## See also + +- [CgBI file format](http://iphonedevwiki.net/index.php/CgBI_file_format) diff --git a/vendor/github.com/andrianbdn/iospng/iospng.go b/vendor/github.com/andrianbdn/iospng/iospng.go new file mode 100644 index 0000000..75bcdf2 --- /dev/null +++ b/vendor/github.com/andrianbdn/iospng/iospng.go @@ -0,0 +1,217 @@ +package iospng + +import ( + "io" + "bytes" + "encoding/binary" + "hash/crc32" + "compress/zlib" + "io/ioutil" + "errors" +) + +var ( + ErrPngHeader = errors.New("Not a Png"); + ErrImageData = errors.New("Unexpected amount of image data") +) + + +type pngChunk struct { + chunkLength, chunkCRC uint32 + chunkType, chunkData []byte +} + +func decodePngData(data []byte) ([]byte, error) { + + var zbuf bytes.Buffer + zbuf.Write([]byte{0x78, 0x1}) // looks like a good zlib header + zbuf.Write(data) + zbuf.Write([]byte{0,0,0,0}) // don't know CRC, will get zlib.ErrChecksum + + reader, err := zlib.NewReader(&zbuf) + if err != nil { + return nil, err + } + defer reader.Close() + + dat, err := ioutil.ReadAll(reader) + + if err != zlib.ErrChecksum { + return nil, err + } + return dat, nil +} + +func (p *pngChunk) write(writer io.Writer, needCrc bool) error { + if needCrc { + crc := crc32.NewIEEE() + crc.Write(p.chunkType) + crc.Write(p.chunkData) + p.chunkCRC = crc.Sum32() + } + + chunkLength := uint32(len(p.chunkData)) + err := binary.Write(writer, binary.BigEndian, &chunkLength) + if err != nil { + return err + } + _, err = writer.Write(p.chunkType) + if err != nil { + return err + } + _, err = writer.Write(p.chunkData) + if err != nil { + return err + } + err = binary.Write(writer, binary.BigEndian, &p.chunkCRC) + if err != nil { + return err + } + return nil +} + +func (p *pngChunk) read(reader io.Reader) error { + + if err := binary.Read(reader, binary.BigEndian, &p.chunkLength); err != nil { + return err + } + + p.chunkType = make([]byte, 4) + + if _, err := io.ReadFull(reader, p.chunkType); err != nil { + return err + } + + p.chunkData = make([]byte, p.chunkLength) + + if _, err := io.ReadFull(reader, p.chunkData); err != nil { + return err + } + + if err := binary.Read(reader, binary.BigEndian, &p.chunkCRC); err != nil { + return err + } + + return nil +} + +func (p *pngChunk) is(kind string) bool { + return string(p.chunkType) == kind +} + +func rawImageFix(w, h int, raw []byte) error { + if len(raw) != w*h*4 + h { + return ErrImageData + } + + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + // we expect this PNG data + // to be 4 bytes per pixel + // 1st byte in each row is filter + row := y*w*4 + y; + col := x*4 + 1 + + b := raw[row+col+0] + g := raw[row+col+1] + r := raw[row+col+2] + a := raw[row+col+3] + + // de-multiplying + r = uint8(float64(r) * 255 / float64(a)) + g = uint8(float64(g) * 255 / float64(a)) + b = uint8(float64(b) * 255 / float64(a)) + + raw[row+col+0] = r + raw[row+col+1] = g + raw[row+col+2] = b + + } + } + return nil +} + +// This function actually does everything: +// reads PNG from reader and in case it is iOS-optimized, +// reverts optimization. +// +// Function does not change data if PNG does not have CgBI chunk. +func PngRevertOptimization(reader io.Reader, writer io.Writer) error { + header := make([]byte, 8) + if _, err := io.ReadFull(reader, header); err != nil { + return err + } + + if bytes.Compare([]byte("\x89PNG\r\n\x1a\n"), header) != 0 { + return ErrPngHeader + } + + writer.Write(header) + + var w, h int; + var datbuf bytes.Buffer + optimized := false + + for { + var chunk pngChunk + if err := chunk.read(reader); err != nil { + return err + } + + + switch { + + case chunk.is("IHDR"): + w = int(binary.BigEndian.Uint32(chunk.chunkData[:4])) + h = int(binary.BigEndian.Uint32(chunk.chunkData[4:8])) + + + case chunk.is("CgBI"): + optimized = true + continue; + + case chunk.is("IDAT"): + if optimized { + datbuf.Write(chunk.chunkData) + continue; + } + + + case chunk.is("IEND"): + if optimized { + + raw, err := decodePngData(datbuf.Bytes()) + if err != nil { + return err + } + + + if err = rawImageFix(w, h, raw); err != nil { + return err + } + + var zdatbuf bytes.Buffer + zwrite := zlib.NewWriter(&zdatbuf) + zwrite.Write(raw) + zwrite.Close() + + chunk.chunkType = []byte("IDAT") + chunk.chunkData = zdatbuf.Bytes() + err = chunk.write(writer, true) + + chunk.chunkType = []byte("IEND") + chunk.chunkData = []byte{} + err = chunk.write(writer, true) + + return nil + } + } + + if err := chunk.write(writer, false); err != nil { + return nil + } + + } + + return nil +} diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE new file mode 100644 index 0000000..835ba3e --- /dev/null +++ b/vendor/github.com/pkg/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney +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. + +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 HOLDER 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. diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md new file mode 100644 index 0000000..6483ba2 --- /dev/null +++ b/vendor/github.com/pkg/errors/README.md @@ -0,0 +1,52 @@ +# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge) + +Package errors provides simple error handling primitives. + +`go get github.com/pkg/errors` + +The traditional error handling idiom in Go is roughly akin to +```go +if err != nil { + return err +} +``` +which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. + +## Adding context to an error + +The errors.Wrap function returns a new error that adds context to the original error. For example +```go +_, err := ioutil.ReadAll(r) +if err != nil { + return errors.Wrap(err, "read failed") +} +``` +## Retrieving the cause of an error + +Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. +```go +type causer interface { + Cause() error +} +``` +`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: +```go +switch err := errors.Cause(err).(type) { +case *MyError: + // handle specifically +default: + // unknown error +} +``` + +[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). + +## Contributing + +We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. + +Before proposing a change, please discuss your change by raising an issue. + +## License + +BSD-2-Clause diff --git a/vendor/github.com/pkg/errors/appveyor.yml b/vendor/github.com/pkg/errors/appveyor.yml new file mode 100644 index 0000000..a932ead --- /dev/null +++ b/vendor/github.com/pkg/errors/appveyor.yml @@ -0,0 +1,32 @@ +version: build-{build}.{branch} + +clone_folder: C:\gopath\src\github.com\pkg\errors +shallow_clone: true # for startup speed + +environment: + GOPATH: C:\gopath + +platform: + - x64 + +# http://www.appveyor.com/docs/installed-software +install: + # some helpful output for debugging builds + - go version + - go env + # pre-installed MinGW at C:\MinGW is 32bit only + # but MSYS2 at C:\msys64 has mingw64 + - set PATH=C:\msys64\mingw64\bin;%PATH% + - gcc --version + - g++ --version + +build_script: + - go install -v ./... + +test_script: + - set PATH=C:\gopath\bin;%PATH% + - go test -v ./... + +#artifacts: +# - path: '%GOPATH%\bin\*.exe' +deploy: off diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 0000000..842ee80 --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,269 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error by recording a stack trace at the point Wrap is called, +// and the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// If additional control is required the errors.WithStack and errors.WithMessage +// functions destructure errors.Wrap into its component operations of annotating +// an error with a stack trace and an a message, respectively. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// causer interface is not exported by this package, but is considered a part +// of stable public API. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported +// +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface. +// +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } +// +// Where errors.StackTrace is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } +// +// stackTracer interface is not exported by this package, but is considered a part +// of stable public API. +// +// See the documentation for Frame.Format for more details. +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func (f *fundamental) Error() string { return f.msg } + +func (f *fundamental) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, f.msg) + f.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) + } +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +func WithStack(err error) error { + if err == nil { + return nil + } + return &withStack{ + err, + callers(), + } +} + +type withStack struct { + error + *stack +} + +func (w *withStack) Cause() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) + } +} + +// Wrap returns an error annotating err with a stack trace +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is call, and the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + } +} + +type withMessage struct { + cause error + msg string +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go new file mode 100644 index 0000000..2874a04 --- /dev/null +++ b/vendor/github.com/pkg/errors/stack.go @@ -0,0 +1,147 @@ +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strings" +) + +// Frame represents a program counter inside a stack frame. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s function name and path of source file relative to the compile time +// GOPATH separated by \n\t (\n\t) +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + pc := f.pc() + fn := runtime.FuncForPC(pc) + if fn == nil { + io.WriteString(s, "unknown") + } else { + file, _ := fn.FileLine(pc) + fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + } + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + fmt.Fprintf(s, "%d", f.line()) + case 'n': + name := runtime.FuncForPC(f.pc()).Name() + io.WriteString(s, funcname(name)) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +// Format formats the stack of Frames according to the fmt.Formatter interface. +// +// %s lists source files for each Frame in the stack +// %v lists the source file and line number for each Frame in the stack +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+v Prints filename, function, and line number for each Frame in the stack. +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + fmt.Fprintf(s, "%v", []Frame(st)) + } + case 's': + fmt.Fprintf(s, "%s", []Frame(st)) + } +} + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} diff --git a/vendor/github.com/shogo82148/androidbinary/LICENSE b/vendor/github.com/shogo82148/androidbinary/LICENSE new file mode 100644 index 0000000..b19693d --- /dev/null +++ b/vendor/github.com/shogo82148/androidbinary/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Ichinose Shogo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/shogo82148/androidbinary/README.md b/vendor/github.com/shogo82148/androidbinary/README.md new file mode 100644 index 0000000..4f06a98 --- /dev/null +++ b/vendor/github.com/shogo82148/androidbinary/README.md @@ -0,0 +1,75 @@ +androidbinary +===== + +[![Build Status](https://travis-ci.org/shogo82148/androidbinary.svg?branch=master)](https://travis-ci.org/shogo82148/androidbinary) +[![GoDoc](https://godoc.org/github.com/shogo82148/androidbinary?status.svg)](https://godoc.org/github.com/shogo82148/androidbinary) + +Android binary file parser + +## High Level API + +### Parse APK files + +``` go +package main + +import ( + "github.com/shogo82148/androidbinary/apk" +) + +func main() { + pkg, _ := apk.OpenFile("your-android-app.apk") + defer pkg.Close() + + icon, _ := apk.Icon(nil) // returns the icon of APK as image.Image + pkgName := pkg.PackageName() // returns the pakcage name +} +``` + +## Low Lebel API + +### Parse XML binary + +``` go +package main + +import ( + "encoding/xml" + + "github.com/shogo82148/androidbinary" + "github.com/shogo82148/androidbinary/apk" +) + +func main() { + f, _ := os.Open("AndroidManifest.xml") + xml, _ := androidbinary.NewXMLFile(f) + reader := xml.Reader() + + // read XML from reader + var manifest apk.Manifest + data, _ := ioutil.ReadAll(reader) + xml.Unmarshal(data, &manifest) +} +``` + +### Parse Resource files + +``` go +package main + +import ( + "fmt" + "github.com/shogo82148/androidbinary" +) + +func main() { + f, _ := os.Open("resources.arsc") + rsc, _ := androidbinary.NewTableFile(f) + resorce, _ := rsc.GetResource(androidbinary.ResID(0xCAFEBABE), nil) + fmt.Println(resource) +} +``` + +## License + +This software is released under the MIT License, see LICENSE. diff --git a/vendor/github.com/shogo82148/androidbinary/apk/apk.go b/vendor/github.com/shogo82148/androidbinary/apk/apk.go new file mode 100644 index 0000000..942bedf --- /dev/null +++ b/vendor/github.com/shogo82148/androidbinary/apk/apk.go @@ -0,0 +1,178 @@ +package apk + +import ( + "archive/zip" + "bytes" + "encoding/xml" + "fmt" + "image" + "io" + "io/ioutil" + "os" + "strconv" + + "github.com/pkg/errors" + "github.com/shogo82148/androidbinary" +) + +// Apk is an application package file for android. +type Apk struct { + f *os.File + zipreader *zip.Reader + manifest Manifest + table *androidbinary.TableFile +} + +// OpenFile will open the file specified by filename and return Apk +func OpenFile(filename string) (apk *Apk, err error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + f.Close() + } + }() + fi, err := f.Stat() + if err != nil { + return nil, err + } + apk, err = OpenZipReader(f, fi.Size()) + if err != nil { + return nil, err + } + apk.f = f + return +} + +// OpenZipReader has same arguments like zip.NewReader +func OpenZipReader(r io.ReaderAt, size int64) (*Apk, error) { + zipreader, err := zip.NewReader(r, size) + if err != nil { + return nil, err + } + apk := &Apk{ + zipreader: zipreader, + } + if err = apk.parseManifest(); err != nil { + return nil, errors.Wrap(err, "parse-manifest") + } + if err = apk.parseResources(); err != nil { + return nil, err + } + return apk, nil +} + +// Close is avaliable only if apk is created with OpenFile +func (k *Apk) Close() error { + if k.f == nil { + return nil + } + return k.f.Close() +} + +// Icon returns the icon image of the APK. +func (k *Apk) Icon(resConfig *androidbinary.ResTableConfig) (image.Image, error) { + iconPath := k.getResource(k.manifest.App.Icon, resConfig) + if androidbinary.IsResID(iconPath) { + return nil, errors.New("unable to convert icon-id to icon path") + } + imgData, err := k.readZipFile(iconPath) + if err != nil { + return nil, err + } + m, _, err := image.Decode(bytes.NewReader(imgData)) + return m, err +} + +// Label returns the label of the APK. +func (k *Apk) Label(resConfig *androidbinary.ResTableConfig) (s string, err error) { + s = k.getResource(k.manifest.App.Label, resConfig) + if androidbinary.IsResID(s) { + err = errors.New("unable to convert label-id to string") + } + return +} + +// Manifest returns the manifest of the APK. +func (k *Apk) Manifest() Manifest { + return k.manifest +} + +// PackageName returns the package name of the APK. +func (k *Apk) PackageName() string { + return k.manifest.Package +} + +// MainAcitivty returns the name of the main activity. +func (k *Apk) MainAcitivty() (activity string, err error) { + for _, act := range k.manifest.App.Activity { + for _, intent := range act.IntentFilter { + if intent.Action.Name == "android.intent.action.MAIN" && + intent.Category.Name == "android.intent.category.LAUNCHER" { + return act.Name, nil + } + } + } + return "", errors.New("No main activity found") +} + +func (k *Apk) parseManifest() error { + xmlData, err := k.readZipFile("AndroidManifest.xml") + if err != nil { + return errors.Wrap(err, "read-manifest.xml") + } + xmlfile, err := androidbinary.NewXMLFile(bytes.NewReader(xmlData)) + if err != nil { + return errors.Wrap(err, "parse-xml") + } + reader := xmlfile.Reader() + data, err := ioutil.ReadAll(reader) + if err != nil { + return err + } + return xml.Unmarshal(data, &k.manifest) +} + +func (k *Apk) parseResources() (err error) { + resData, err := k.readZipFile("resources.arsc") + if err != nil { + return + } + k.table, err = androidbinary.NewTableFile(bytes.NewReader(resData)) + return +} + +func (k *Apk) getResource(id string, resConfig *androidbinary.ResTableConfig) string { + resID, err := androidbinary.ParseResID(id) + if err != nil { + return id + } + val, err := k.table.GetResource(resID, resConfig) + if err != nil { + return id + } + return fmt.Sprintf("%s", val) +} + +func (k *Apk) readZipFile(name string) (data []byte, err error) { + buf := bytes.NewBuffer(nil) + for _, file := range k.zipreader.File { + if file.Name != name { + continue + } + rc, er := file.Open() + if er != nil { + err = er + return + } + defer rc.Close() + _, err = io.Copy(buf, rc) + if err != nil { + return + } + return buf.Bytes(), nil + } + return nil, fmt.Errorf("File %s not found", strconv.Quote(name)) +} diff --git a/vendor/github.com/shogo82148/androidbinary/apk/apkxml.go b/vendor/github.com/shogo82148/androidbinary/apk/apkxml.go new file mode 100644 index 0000000..791dc89 --- /dev/null +++ b/vendor/github.com/shogo82148/androidbinary/apk/apkxml.go @@ -0,0 +1,82 @@ +package apk + +// Instrumentation is an application instrumentation code. +type Instrumentation struct { + Name string `xml:"name,attr"` + Target string `xml:"targetPackage,attr"` + HandleProfiling bool `xml:"handleProfiling,attr"` + FunctionalTest bool `xml:"functionalTest,attr"` +} + +// ActivityAction is an action of an activity. +type ActivityAction struct { + Name string `xml:"name,attr"` +} + +// ActivityCategory is a category of an activity. +type ActivityCategory struct { + Name string `xml:"name,attr"` +} + +// ActivityIntentFilter is an intent filter of an activity. +type ActivityIntentFilter struct { + Action ActivityAction `xml:"action"` + Category ActivityCategory `xml:"category"` +} + +// AppActivity is an activity in an application. +type AppActivity struct { + Theme string `xml:"theme,attr"` + Name string `xml:"name,attr"` + Label string `xml:"label,attr"` + IntentFilter []ActivityIntentFilter `xml:"intent-filter"` +} + +// Application is an application in an APK. +type Application struct { + AllowTaskReparenting bool `xml:"allowTaskReparenting,attr"` + AllowBackup bool `xml:"allowBackup,attr"` + BackupAgent string `xml:"backupAgent,attr"` + Debuggable bool `xml:"debuggable,attr"` + Description string `xml:"description,attr"` + Enabled bool `xml:"enabled,attr"` + HasCode bool `xml:"hasCode,attr"` + HardwareAccelerated bool `xml:"hardwareAccelerated,attr"` + Icon string `xml:"icon,attr"` + KillAfterRestore bool `xml:"killAfterRestore,attr"` + LargeHeap bool `xml:"largeHeap,attr"` + Label string `xml:"label,attr"` + Logo int `xml:"logo,attr"` + ManageSpaceActivity string `xml:"manageSpaceActivity,attr"` + Name string `xml:"name,attr"` + Permission string `xml:"permission,attr"` + Persistent bool `xml:"persistent,attr"` + Process string `xml:"process,attr"` + RestoreAnyVersion bool `xml:"restoreAnyVersion,attr"` + RequiredAccountType string `xml:"requiredAccountType,attr"` + RestrictedAccountType string `xml:"restrictedAccountType,attr"` + SupportsRtl bool `xml:"supportsRtl,attr"` + TaskAffinity string `xml:"taskAffinity,attr"` + TestOnly bool `xml:"testOnly,attr"` + Theme string `xml:"theme,attr"` + UIOptions string `xml:"uiOptions,attr"` + VMSafeMode bool `xml:"vmSafeMode,attr"` + Activity []AppActivity `xml:"activity"` +} + +// UsesSDK is target SDK version. +type UsesSDK struct { + Min int `xml:"minSdkVersion,attr"` + Target int `xml:"targetSdkVersion,attr"` + Max int `xml:"maxSdkVersion,attr"` +} + +// Manifest is a manifest of an APK. +type Manifest struct { + Package string `xml:"package,attr"` + VersionCode int `xml:"versionCode,attr"` + VersionName string `xml:"versionName,attr"` + App Application `xml:"application"` + Instrument Instrumentation `xml:"instrumentation"` + SDK UsesSDK `xml:"uses-sdk"` +} diff --git a/vendor/github.com/shogo82148/androidbinary/common.go b/vendor/github.com/shogo82148/androidbinary/common.go new file mode 100644 index 0000000..d760158 --- /dev/null +++ b/vendor/github.com/shogo82148/androidbinary/common.go @@ -0,0 +1,258 @@ +package androidbinary + +import ( + "bytes" + "encoding/binary" + "io" + "unicode/utf16" +) + +// ChunkType is a type of a resource chunk. +type ChunkType uint16 + +// Chunk types. +const ( + ResNullChunkType ChunkType = 0x0000 + ResStringPoolChunkType ChunkType = 0x0001 + ResTableChunkType ChunkType = 0x0002 + ResXMLChunkType ChunkType = 0x0003 + + // Chunk types in RES_XML_TYPE + ResXMLFirstChunkType ChunkType = 0x0100 + ResXMLStartNamespaceType ChunkType = 0x0100 + ResXMLEndNamespaceType ChunkType = 0x0101 + ResXMLStartElementType ChunkType = 0x0102 + ResXMLEndElementType ChunkType = 0x0103 + ResXMLCDataType ChunkType = 0x0104 + ResXMLLastChunkType ChunkType = 0x017f + + // This contains a uint32_t array mapping strings in the string + // pool back to resource identifiers. It is optional. + ResXMLResourceMapType ChunkType = 0x0180 + + // Chunk types in RES_TABLE_TYPE + ResTablePackageType ChunkType = 0x0200 + ResTableTypeType ChunkType = 0x0201 + ResTableTypeSpecType ChunkType = 0x0202 +) + +// ResChunkHeader is a header of a resource chunk. +type ResChunkHeader struct { + Type ChunkType + HeaderSize uint16 + Size uint32 +} + +// Flags are flags for string pool header. +type Flags uint32 + +// the values of Flags. +const ( + SortedFlag Flags = 1 << 0 + UTF8Flag Flags = 1 << 8 +) + +// ResStringPoolHeader is a chunk header of string pool. +type ResStringPoolHeader struct { + Header ResChunkHeader + StringCount uint32 + StyleCount uint32 + Flags Flags + StringStart uint32 + StylesStart uint32 +} + +// ResStringPoolSpan is a span of style information associated with +// a string in the pool. +type ResStringPoolSpan struct { + FirstChar, LastChar uint32 +} + +// ResStringPool is a string pool resrouce. +type ResStringPool struct { + Header ResStringPoolHeader + Strings []string + Styles []ResStringPoolSpan +} + +// NilResStringPoolRef is nil reference for string pool. +const NilResStringPoolRef = ResStringPoolRef(0xFFFFFFFF) + +// ResStringPoolRef is a type representing a reference to a string. +type ResStringPoolRef uint32 + +// DataType is a type of the data value. +type DataType uint8 + +// The constants for DataType +const ( + TypeNull DataType = 0x00 + TypeReference DataType = 0x01 + TypeAttribute DataType = 0x02 + TypeString DataType = 0x03 + TypeFloat DataType = 0x04 + TypeDemention DataType = 0x05 + TypeFraction DataType = 0x06 + TypeFirstInt DataType = 0x10 + TypeIntDec DataType = 0x10 + TypeIntHex DataType = 0x11 + TypeIntBoolean DataType = 0x12 + TypeFirstColorInt DataType = 0x1c + TypeIntColorARGB8 DataType = 0x1c + TypeIntColorRGB8 DataType = 0x1d + TypeIntColorARGB4 DataType = 0x1e + TypeIntColorRGB4 DataType = 0x1f + TypeLastColorInt DataType = 0x1f + TypeLastInt DataType = 0x1f +) + +// ResValue is a representation of a value in a resource +type ResValue struct { + Size uint16 + Res0 uint8 + DataType DataType + Data uint32 +} + +// GetString returns a string referenced by ref. +func (pool *ResStringPool) GetString(ref ResStringPoolRef) string { + return pool.Strings[int(ref)] +} + +func readStringPool(sr *io.SectionReader) (*ResStringPool, error) { + sp := new(ResStringPool) + if err := binary.Read(sr, binary.LittleEndian, &sp.Header); err != nil { + return nil, err + } + + stringStarts := make([]uint32, sp.Header.StringCount) + if err := binary.Read(sr, binary.LittleEndian, stringStarts); err != nil { + return nil, err + } + + styleStarts := make([]uint32, sp.Header.StyleCount) + if err := binary.Read(sr, binary.LittleEndian, styleStarts); err != nil { + return nil, err + } + + sp.Strings = make([]string, sp.Header.StringCount) + for i, start := range stringStarts { + var str string + var err error + if _, err := sr.Seek(int64(sp.Header.StringStart+start), seekStart); err != nil { + return nil, err + } + if (sp.Header.Flags & UTF8Flag) == 0 { + str, err = readUTF16(sr) + } else { + str, err = readUTF8(sr) + } + if err != nil { + return nil, err + } + sp.Strings[i] = str + } + + sp.Styles = make([]ResStringPoolSpan, sp.Header.StyleCount) + for i, start := range styleStarts { + if _, err := sr.Seek(int64(sp.Header.StylesStart+start), seekStart); err != nil { + return nil, err + } + if err := binary.Read(sr, binary.LittleEndian, &sp.Styles[i]); err != nil { + return nil, err + } + } + + return sp, nil +} + +func readUTF16(sr *io.SectionReader) (string, error) { + // read lenth of string + size, err := readUTF16length(sr) + if err != nil { + return "", err + } + + // read string value + buf := make([]uint16, size) + if err := binary.Read(sr, binary.LittleEndian, buf); err != nil { + return "", err + } + return string(utf16.Decode(buf)), nil +} + +func readUTF16length(sr *io.SectionReader) (int, error) { + var size int + var first, second uint16 + if err := binary.Read(sr, binary.LittleEndian, &first); err != nil { + return 0, err + } + if (first & 0x8000) != 0 { + if err := binary.Read(sr, binary.LittleEndian, &second); err != nil { + return 0, err + } + size = (int(first&0x7FFF) << 16) + int(second) + } else { + size = int(first) + } + return size, nil +} + +func readUTF8(sr *io.SectionReader) (string, error) { + // skip utf16 length + _, err := readUTF8length(sr) + if err != nil { + return "", err + } + + // read lenth of string + size, err := readUTF8length(sr) + if err != nil { + return "", err + } + + buf := make([]uint8, size) + if err := binary.Read(sr, binary.LittleEndian, buf); err != nil { + return "", err + } + return string(buf), nil +} + +func readUTF8length(sr *io.SectionReader) (int, error) { + var size int + var first, second uint8 + if err := binary.Read(sr, binary.LittleEndian, &first); err != nil { + return 0, err + } + if (first & 0x80) != 0 { + if err := binary.Read(sr, binary.LittleEndian, &second); err != nil { + return 0, err + } + size = (int(first&0x7F) << 8) + int(second) + } else { + size = int(first) + } + return size, nil +} + +func newZeroFilledReader(r io.Reader, actual int64, expected int64) (io.Reader, error) { + if actual >= expected { + // no need to fill + return r, nil + } + + // read `actual' bytes from r, and + buf := new(bytes.Buffer) + if _, err := io.CopyN(buf, r, actual); err != nil { + return nil, err + } + + // fill zero until `expected' bytes + for i := actual; i < expected; i++ { + if err := buf.WriteByte(0x00); err != nil { + return nil, err + } + } + + return buf, nil +} diff --git a/vendor/github.com/shogo82148/androidbinary/const_fallback.go b/vendor/github.com/shogo82148/androidbinary/const_fallback.go new file mode 100644 index 0000000..1bf4d5f --- /dev/null +++ b/vendor/github.com/shogo82148/androidbinary/const_fallback.go @@ -0,0 +1,7 @@ +// +build !go17 + +package androidbinary + +import "os" + +const seekStart = os.SEEK_SET diff --git a/vendor/github.com/shogo82148/androidbinary/const_go17.go b/vendor/github.com/shogo82148/androidbinary/const_go17.go new file mode 100644 index 0000000..2ea0d24 --- /dev/null +++ b/vendor/github.com/shogo82148/androidbinary/const_go17.go @@ -0,0 +1,7 @@ +// +build go17 + +package androidbinary + +import "io" + +const seekStart = io.SeekStart diff --git a/vendor/github.com/shogo82148/androidbinary/table.go b/vendor/github.com/shogo82148/androidbinary/table.go new file mode 100644 index 0000000..13f7178 --- /dev/null +++ b/vendor/github.com/shogo82148/androidbinary/table.go @@ -0,0 +1,1093 @@ +package androidbinary + +import ( + "encoding/binary" + "fmt" + "io" + "strconv" + "strings" + "unsafe" +) + +// ResID is ID for resources. +type ResID uint32 + +// TableFile is a resrouce table file. +type TableFile struct { + stringPool *ResStringPool + tablePackages map[uint32]*TablePackage +} + +// ResTableHeader is a header of TableFile. +type ResTableHeader struct { + Header ResChunkHeader + PackageCount uint32 +} + +// ResTablePackage is a header of table packages. +type ResTablePackage struct { + Header ResChunkHeader + ID uint32 + Name [128]uint16 + TypeStrings uint32 + LastPublicType uint32 + KeyStrings uint32 + LastPublicKey uint32 +} + +// TablePackage is a table package. +type TablePackage struct { + Header ResTablePackage + TypeStrings *ResStringPool + KeyStrings *ResStringPool + TableTypes []*TableType +} + +// ResTableType is a type of a table. +type ResTableType struct { + Header ResChunkHeader + ID uint8 + Res0 uint8 + Res1 uint16 + EntryCount uint32 + EntriesStart uint32 + Config ResTableConfig +} + +// ScreenLayout describes screen layout. +type ScreenLayout uint8 + +// ScreenLayout bits +const ( + MaskScreenSize ScreenLayout = 0x0f + ScreenSizeAny ScreenLayout = 0x01 + ScreenSizeSmall ScreenLayout = 0x02 + ScreenSizeNormal ScreenLayout = 0x03 + ScreenSizeLarge ScreenLayout = 0x04 + ScreenSizeXLarge ScreenLayout = 0x05 + + MaskScreenLong ScreenLayout = 0x30 + ShiftScreenLong = 4 + ScreenLongAny ScreenLayout = 0x00 + ScreenLongNo ScreenLayout = 0x10 + ScreenLongYes ScreenLayout = 0x20 + + MaskLayoutDir ScreenLayout = 0xC0 + ShiftLayoutDir = 6 + LayoutDirAny ScreenLayout = 0x00 + LayoutDirLTR ScreenLayout = 0x40 + LayoutDirRTL ScreenLayout = 0x80 +) + +// UIMode describes UI mode. +type UIMode uint8 + +// UIMode bits +const ( + MaskUIModeType UIMode = 0x0f + UIModeTypeAny UIMode = 0x01 + UIModeTypeNormal UIMode = 0x02 + UIModeTypeDesk UIMode = 0x03 + UIModeTypeCar UIMode = 0x04 + + MaskUIModeNight UIMode = 0x30 + ShiftUIModeNight = 4 + UIModeNightAny UIMode = 0x00 + UIModeNightNo UIMode = 0x10 + UIModeNightYes UIMode = 0x20 +) + +// InputFlags are input flags. +type InputFlags uint8 + +// input flags +const ( + MaskKeysHidden InputFlags = 0x03 + KeysHiddenAny InputFlags = 0x00 + KeysHiddenNo InputFlags = 0x01 + KeysHiddenYes InputFlags = 0x02 + KeysHiddenSoft InputFlags = 0x03 + + MaskNavHidden InputFlags = 0x0c + NavHiddenAny InputFlags = 0x00 + NavHiddenNo InputFlags = 0x04 + NavHiddenYes InputFlags = 0x08 +) + +// ResTableConfig is a configuration of a table. +type ResTableConfig struct { + Size uint32 + // imsi + Mcc uint16 + Mnc uint16 + + // locale + Language [2]uint8 + Country [2]uint8 + + // screen type + Orientation uint8 + Touchscreen uint8 + Density uint16 + + // inout + Keyboard uint8 + Navigation uint8 + InputFlags InputFlags + InputPad0 uint8 + + // screen size + ScreenWidth uint16 + ScreenHeight uint16 + + // version + SDKVersion uint16 + MinorVersion uint16 + + // screen config + ScreenLayout ScreenLayout + UIMode UIMode + SmallestScreenWidthDp uint16 + + // screen size dp + ScreenWidthDp uint16 + ScreenHeightDp uint16 +} + +// TableType is a collection of resource entries for a particular resource data type. +type TableType struct { + Header *ResTableType + Entries []TableEntry +} + +// ResTableEntry is the beginning of information about an entry in the resource table. +type ResTableEntry struct { + Size uint16 + Flags uint16 + Key ResStringPoolRef +} + +// TableEntry is a entry in a recource table. +type TableEntry struct { + Key *ResTableEntry + Value *ResValue + Flags uint32 +} + +// ResTableTypeSpec is specification of the resources defined by a particular type. +type ResTableTypeSpec struct { + Header ResChunkHeader + ID uint8 + Res0 uint8 + Res1 uint16 + EntryCount uint32 +} + +// IsResID returns whether s is ResId. +func IsResID(s string) bool { + return strings.HasPrefix(s, "@0x") +} + +// ParseResID parses ResId. +func ParseResID(s string) (ResID, error) { + if !IsResID(s) { + return 0, fmt.Errorf("androidbinary: %s is not ResID", s) + } + id, err := strconv.ParseUint(s[3:], 16, 32) + if err != nil { + return 0, err + } + return ResID(id), nil +} + +func (id ResID) String() string { + return fmt.Sprintf("@0x%08X", uint32(id)) +} + +// Package returns the package index of id. +func (id ResID) Package() uint32 { + return uint32(id) >> 24 +} + +// Type returns the type index of id. +func (id ResID) Type() int { + return (int(id) >> 16) & 0xFF +} + +// Entry returns the entry index of id. +func (id ResID) Entry() int { + return int(id) & 0xFFFF +} + +// NewTableFile returns new TableFile. +func NewTableFile(r io.ReaderAt) (*TableFile, error) { + f := new(TableFile) + sr := io.NewSectionReader(r, 0, 1<<63-1) + + header := new(ResTableHeader) + binary.Read(sr, binary.LittleEndian, header) + f.tablePackages = make(map[uint32]*TablePackage) + + offset := int64(header.Header.HeaderSize) + for offset < int64(header.Header.Size) { + chunkHeader, err := f.readChunk(sr, offset) + if err != nil { + return nil, err + } + offset += int64(chunkHeader.Size) + } + return f, nil +} + +func (f *TableFile) findPackage(id uint32) *TablePackage { + if f == nil { + return nil + } + return f.tablePackages[id] +} + +func (p *TablePackage) findEntry(typeIndex, entryIndex int, config *ResTableConfig) TableEntry { + var best *TableType + for _, t := range p.TableTypes { + switch { + case int(t.Header.ID) != typeIndex: + // nothing to do + case !t.Header.Config.Match(config): + // nothing to do + case entryIndex >= len(t.Entries): + // nothing to do + case t.Entries[entryIndex].Value == nil: + // nothing to do + case best == nil || t.Header.Config.IsBetterThan(&best.Header.Config, config): + best = t + } + } + if best == nil || entryIndex >= len(best.Entries) { + return TableEntry{} + } + return best.Entries[entryIndex] +} + +// GetResource returns a resrouce referenced by id. +func (f *TableFile) GetResource(id ResID, config *ResTableConfig) (interface{}, error) { + p := f.findPackage(id.Package()) + if p == nil { + return nil, fmt.Errorf("androidbinary: package 0x%02X not found", id.Package()) + } + e := p.findEntry(id.Type(), id.Entry(), config) + v := e.Value + if v == nil { + return nil, fmt.Errorf("androidbinary: entry 0x%04X not found", id.Entry()) + } + switch v.DataType { + case TypeNull: + return nil, nil + case TypeString: + return f.GetString(ResStringPoolRef(v.Data)), nil + case TypeIntDec: + return v.Data, nil + case TypeIntHex: + return v.Data, nil + case TypeIntBoolean: + return v.Data != 0, nil + } + return v.Data, nil +} + +// GetString returns a string referenced by ref. +func (f *TableFile) GetString(ref ResStringPoolRef) string { + return f.stringPool.GetString(ref) +} + +func (f *TableFile) readChunk(r io.ReaderAt, offset int64) (*ResChunkHeader, error) { + sr := io.NewSectionReader(r, offset, 1<<63-1-offset) + chunkHeader := &ResChunkHeader{} + if _, err := sr.Seek(0, seekStart); err != nil { + return nil, err + } + if err := binary.Read(sr, binary.LittleEndian, chunkHeader); err != nil { + return nil, err + } + + var err error + if _, err := sr.Seek(0, seekStart); err != nil { + return nil, err + } + switch chunkHeader.Type { + case ResStringPoolChunkType: + f.stringPool, err = readStringPool(sr) + case ResTablePackageType: + var tablePackage *TablePackage + tablePackage, err = readTablePackage(sr) + f.tablePackages[tablePackage.Header.ID] = tablePackage + } + if err != nil { + return nil, err + } + + return chunkHeader, nil +} + +func readTablePackage(sr *io.SectionReader) (*TablePackage, error) { + tablePackage := new(TablePackage) + header := new(ResTablePackage) + if err := binary.Read(sr, binary.LittleEndian, header); err != nil { + return nil, err + } + tablePackage.Header = *header + + srTypes := io.NewSectionReader(sr, int64(header.TypeStrings), int64(header.Header.Size-header.TypeStrings)) + if typeStrings, err := readStringPool(srTypes); err == nil { + tablePackage.TypeStrings = typeStrings + } else { + return nil, err + } + + srKeys := io.NewSectionReader(sr, int64(header.KeyStrings), int64(header.Header.Size-header.KeyStrings)) + if keyStrings, err := readStringPool(srKeys); err == nil { + tablePackage.KeyStrings = keyStrings + } else { + return nil, err + } + + offset := int64(header.Header.HeaderSize) + for offset < int64(header.Header.Size) { + chunkHeader := &ResChunkHeader{} + if _, err := sr.Seek(offset, seekStart); err != nil { + return nil, err + } + if err := binary.Read(sr, binary.LittleEndian, chunkHeader); err != nil { + return nil, err + } + + var err error + chunkReader := io.NewSectionReader(sr, offset, int64(chunkHeader.Size)) + if _, err := sr.Seek(offset, seekStart); err != nil { + return nil, err + } + switch chunkHeader.Type { + case ResTableTypeType: + var tableType *TableType + tableType, err = readTableType(chunkHeader, chunkReader) + tablePackage.TableTypes = append(tablePackage.TableTypes, tableType) + case ResTableTypeSpecType: + _, err = readTableTypeSpec(chunkReader) + } + if err != nil { + return nil, err + } + offset += int64(chunkHeader.Size) + } + + return tablePackage, nil +} + +func readTableType(chunkHeader *ResChunkHeader, sr *io.SectionReader) (*TableType, error) { + // TableType header may be omitted + header := new(ResTableType) + if _, err := sr.Seek(0, seekStart); err != nil { + return nil, err + } + buf, err := newZeroFilledReader(sr, int64(chunkHeader.HeaderSize), int64(unsafe.Sizeof(*header))) + if err != nil { + return nil, err + } + if err := binary.Read(buf, binary.LittleEndian, header); err != nil { + return nil, err + } + + entryIndexes := make([]uint32, header.EntryCount) + if _, err := sr.Seek(int64(header.Header.HeaderSize), seekStart); err != nil { + return nil, err + } + if err := binary.Read(sr, binary.LittleEndian, entryIndexes); err != nil { + return nil, err + } + + entries := make([]TableEntry, header.EntryCount) + for i, index := range entryIndexes { + if index == 0xFFFFFFFF { + continue + } + if _, err := sr.Seek(int64(header.EntriesStart+index), seekStart); err != nil { + return nil, err + } + var key ResTableEntry + binary.Read(sr, binary.LittleEndian, &key) + entries[i].Key = &key + + var val ResValue + binary.Read(sr, binary.LittleEndian, &val) + entries[i].Value = &val + } + return &TableType{ + header, + entries, + }, nil +} + +func readTableTypeSpec(sr *io.SectionReader) ([]uint32, error) { + header := new(ResTableTypeSpec) + if err := binary.Read(sr, binary.LittleEndian, header); err != nil { + return nil, err + } + + flags := make([]uint32, header.EntryCount) + if _, err := sr.Seek(int64(header.Header.HeaderSize), seekStart); err != nil { + return nil, err + } + if err := binary.Read(sr, binary.LittleEndian, flags); err != nil { + return nil, err + } + return flags, nil +} + +// IsMoreSpecificThan returns true if c is more specific than o. +func (c *ResTableConfig) IsMoreSpecificThan(o *ResTableConfig) bool { + // nil ResTableConfig is never more specific than any ResTableConfig + if c == nil { + return false + } + if o == nil { + return false + } + + // imsi + if c.Mcc != o.Mcc { + if c.Mcc == 0 { + return false + } + if o.Mnc == 0 { + return true + } + } + if c.Mnc != o.Mnc { + if c.Mnc == 0 { + return false + } + if o.Mnc == 0 { + return true + } + } + + // locale + if diff := c.IsLocaleMoreSpecificThan(o); diff < 0 { + return false + } else if diff > 0 { + return true + } + + // screen layout + if c.ScreenLayout != 0 || o.ScreenLayout != 0 { + if ((c.ScreenLayout ^ o.ScreenLayout) & MaskLayoutDir) != 0 { + if (c.ScreenLayout & MaskLayoutDir) == 0 { + return false + } + if (o.ScreenLayout & MaskLayoutDir) == 0 { + return true + } + } + } + + // smallest screen width dp + if c.SmallestScreenWidthDp != 0 || o.SmallestScreenWidthDp != 0 { + if c.SmallestScreenWidthDp != o.SmallestScreenWidthDp { + if c.SmallestScreenWidthDp == 0 { + return false + } + if o.SmallestScreenWidthDp == 0 { + return true + } + } + } + + // screen size dp + if c.ScreenWidthDp != 0 || o.ScreenWidthDp != 0 || + c.ScreenHeightDp != 0 || o.ScreenHeightDp != 0 { + if c.ScreenWidthDp != o.ScreenWidthDp { + if c.ScreenWidthDp == 0 { + return false + } + if o.ScreenWidthDp == 0 { + return true + } + } + if c.ScreenHeightDp != o.ScreenHeightDp { + if c.ScreenHeightDp == 0 { + return false + } + if o.ScreenHeightDp == 0 { + return true + } + } + } + + // screen layout + if c.ScreenLayout != 0 || o.ScreenLayout != 0 { + if ((c.ScreenLayout ^ o.ScreenLayout) & MaskScreenSize) != 0 { + if (c.ScreenLayout & MaskScreenSize) == 0 { + return false + } + if (o.ScreenLayout & MaskScreenSize) == 0 { + return true + } + } + if ((c.ScreenLayout ^ o.ScreenLayout) & MaskScreenLong) != 0 { + if (c.ScreenLayout & MaskScreenLong) == 0 { + return false + } + if (o.ScreenLayout & MaskScreenLong) == 0 { + return true + } + } + } + + // orientation + if c.Orientation != o.Orientation { + if c.Orientation == 0 { + return false + } + if o.Orientation == 0 { + return true + } + } + + // uimode + if c.UIMode != 0 || o.UIMode != 0 { + diff := c.UIMode ^ o.UIMode + if (diff & MaskUIModeType) != 0 { + if (c.UIMode & MaskUIModeType) == 0 { + return false + } + if (o.UIMode & MaskUIModeType) == 0 { + return true + } + } + if (diff & MaskUIModeNight) != 0 { + if (c.UIMode & MaskUIModeNight) == 0 { + return false + } + if (o.UIMode & MaskUIModeNight) == 0 { + return true + } + } + } + + // touchscreen + if c.Touchscreen != o.Touchscreen { + if c.Touchscreen == 0 { + return false + } + if o.Touchscreen == 0 { + return true + } + } + + // input + if c.InputFlags != 0 || o.InputFlags != 0 { + myKeysHidden := c.InputFlags & MaskKeysHidden + oKeysHidden := o.InputFlags & MaskKeysHidden + if (myKeysHidden ^ oKeysHidden) != 0 { + if myKeysHidden == 0 { + return false + } + if oKeysHidden == 0 { + return true + } + } + myNavHidden := c.InputFlags & MaskNavHidden + oNavHidden := o.InputFlags & MaskNavHidden + if (myNavHidden ^ oNavHidden) != 0 { + if myNavHidden == 0 { + return false + } + if oNavHidden == 0 { + return true + } + } + } + + if c.Keyboard != o.Keyboard { + if c.Keyboard == 0 { + return false + } + if o.Keyboard == 0 { + return true + } + } + + if c.Navigation != o.Navigation { + if c.Navigation == 0 { + return false + } + if o.Navigation == 0 { + return true + } + } + + // screen size + if c.ScreenWidth != 0 || o.ScreenWidth != 0 || + c.ScreenHeight != 0 || o.ScreenHeight != 0 { + if c.ScreenWidth != o.ScreenWidth { + if c.ScreenWidth == 0 { + return false + } + if o.ScreenWidth == 0 { + return true + } + } + if c.ScreenHeight != o.ScreenHeight { + if c.ScreenHeight == 0 { + return false + } + if o.ScreenHeight == 0 { + return true + } + } + } + + //version + if c.SDKVersion != o.SDKVersion { + if c.SDKVersion == 0 { + return false + } + if o.SDKVersion == 0 { + return true + } + } + if c.MinorVersion != o.MinorVersion { + if c.MinorVersion == 0 { + return false + } + if o.MinorVersion == 0 { + return true + } + } + + return false +} + +// IsBetterThan returns true if c is better than o for the r configuration. +func (c *ResTableConfig) IsBetterThan(o *ResTableConfig, r *ResTableConfig) bool { + if r == nil { + return c.IsMoreSpecificThan(o) + } + + // nil ResTableConfig is never better than any ResTableConfig + if c == nil { + return false + } + if o == nil { + return false + } + + // imsi + if c.Mcc != 0 || c.Mnc != 0 || o.Mcc != 0 || o.Mnc != 0 { + if c.Mcc != o.Mcc && r.Mcc != 0 { + return c.Mcc != 0 + } + if c.Mnc != o.Mnc && r.Mnc != 0 { + return c.Mnc != 0 + } + } + + // locale + if c.IsLocaleBetterThan(o, r) { + return true + } + + // screen layout + if c.ScreenLayout != 0 || o.ScreenLayout != 0 { + myLayoutdir := c.ScreenLayout & MaskLayoutDir + oLayoutdir := o.ScreenLayout & MaskLayoutDir + if (myLayoutdir^oLayoutdir) != 0 && (r.ScreenLayout&MaskLayoutDir) != 0 { + return myLayoutdir > oLayoutdir + } + } + + // smallest screen width dp + if c.SmallestScreenWidthDp != 0 || o.SmallestScreenWidthDp != 0 { + if c.SmallestScreenWidthDp != o.SmallestScreenWidthDp { + return c.SmallestScreenWidthDp > o.SmallestScreenWidthDp + } + } + + // screen size dp + if c.ScreenWidthDp != 0 || c.ScreenHeightDp != 0 || o.ScreenWidthDp != 0 || o.ScreenHeightDp != 0 { + myDelta := 0 + otherDelta := 0 + if r.ScreenWidthDp != 0 { + myDelta += int(r.ScreenWidthDp) - int(c.ScreenWidthDp) + otherDelta += int(r.ScreenWidthDp) - int(o.ScreenWidthDp) + } + if r.ScreenHeightDp != 0 { + myDelta += int(r.ScreenHeightDp) - int(c.ScreenHeightDp) + otherDelta += int(r.ScreenHeightDp) - int(o.ScreenHeightDp) + } + if myDelta != otherDelta { + return myDelta < otherDelta + } + } + + // screen layout + if c.ScreenLayout != 0 || o.ScreenLayout != 0 { + mySL := c.ScreenLayout & MaskScreenSize + oSL := o.ScreenLayout & MaskScreenSize + if (mySL^oSL) != 0 && (r.ScreenLayout&MaskScreenSize) != 0 { + fixedMySL := mySL + fixedOSL := oSL + if (r.ScreenLayout & MaskScreenSize) >= ScreenSizeNormal { + if fixedMySL == 0 { + fixedMySL = ScreenSizeNormal + } + if fixedOSL == 0 { + fixedOSL = ScreenSizeNormal + } + } + if fixedMySL == fixedOSL { + return mySL != 0 + } + return fixedMySL > fixedOSL + } + + if ((c.ScreenLayout^o.ScreenLayout)&MaskScreenLong) != 0 && + (r.ScreenLayout&MaskScreenLong) != 0 { + return (c.ScreenLayout & MaskScreenLong) != 0 + } + } + + // orientation + if c.Orientation != o.Orientation && r.Orientation != 0 { + return c.Orientation != 0 + } + + // uimode + if c.UIMode != 0 || o.UIMode != 0 { + diff := c.UIMode ^ o.UIMode + if (diff&MaskUIModeType) != 0 && (r.UIMode&MaskUIModeType) != 0 { + return (c.UIMode & MaskUIModeType) != 0 + } + if (diff&MaskUIModeNight) != 0 && (r.UIMode&MaskUIModeNight) != 0 { + return (c.UIMode & MaskUIModeNight) != 0 + } + } + + // screen type + if c.Density != o.Density { + h := int(c.Density) + if h == 0 { + h = 160 + } + l := int(o.Density) + if l == 0 { + l = 160 + } + blmBigger := true + if l > h { + h, l = l, h + blmBigger = false + } + + reqValue := int(r.Density) + if reqValue == 0 { + reqValue = 160 + } + if reqValue >= h { + return blmBigger + } + if l >= reqValue { + return !blmBigger + } + if (2*l-reqValue)*h > reqValue*reqValue { + return !blmBigger + } + return blmBigger + } + if c.Touchscreen != o.Touchscreen && r.Touchscreen != 0 { + return c.Touchscreen != 0 + } + + // input + if c.InputFlags != 0 || o.InputFlags != 0 { + myKeysHidden := c.InputFlags & MaskKeysHidden + oKeysHidden := o.InputFlags & MaskKeysHidden + reqKeysHidden := r.InputFlags & MaskKeysHidden + if myKeysHidden != oKeysHidden && reqKeysHidden != 0 { + switch { + case myKeysHidden == 0: + return false + case oKeysHidden == 0: + return true + case reqKeysHidden == myKeysHidden: + return true + case reqKeysHidden == oKeysHidden: + return false + } + } + myNavHidden := c.InputFlags & MaskNavHidden + oNavHidden := o.InputFlags & MaskNavHidden + reqNavHidden := r.InputFlags & MaskNavHidden + if myNavHidden != oNavHidden && reqNavHidden != 0 { + switch { + case myNavHidden == 0: + return false + case oNavHidden == 0: + return true + } + } + } + if c.Keyboard != o.Keyboard && r.Keyboard != 0 { + return c.Keyboard != 0 + } + if c.Navigation != o.Navigation && r.Navigation != 0 { + return c.Navigation != 0 + } + + // screen size + if c.ScreenWidth != 0 || c.ScreenHeight != 0 || o.ScreenWidth != 0 || o.ScreenHeight != 0 { + myDelta := 0 + otherDelta := 0 + if r.ScreenWidth != 0 { + myDelta += int(r.ScreenWidth) - int(c.ScreenWidth) + otherDelta += int(r.ScreenWidth) - int(o.ScreenWidth) + } + if r.ScreenHeight != 0 { + myDelta += int(r.ScreenHeight) - int(c.ScreenHeight) + otherDelta += int(r.ScreenHeight) - int(o.ScreenHeight) + } + if myDelta != otherDelta { + return myDelta < otherDelta + } + } + + // version + if c.SDKVersion != 0 || o.MinorVersion != 0 { + if c.SDKVersion != o.SDKVersion && r.SDKVersion != 0 { + return c.SDKVersion > o.SDKVersion + } + if c.MinorVersion != o.MinorVersion && r.MinorVersion != 0 { + return c.MinorVersion != 0 + } + } + + return false +} + +// IsLocaleMoreSpecificThan a positive integer if this config is more specific than o, +// a negative integer if |o| is more specific +// and 0 if they're equally specific. +func (c *ResTableConfig) IsLocaleMoreSpecificThan(o *ResTableConfig) int { + if (c.Language != [2]uint8{} || c.Country != [2]uint8{}) || (o.Language != [2]uint8{} || o.Country != [2]uint8{}) { + if c.Language != o.Language { + if c.Language == [2]uint8{} { + return -1 + } + if o.Language == [2]uint8{} { + return 1 + } + } + + if c.Country != o.Country { + if c.Country == [2]uint8{} { + return -1 + } + if o.Country == [2]uint8{} { + return 1 + } + } + } + return 0 +} + +// IsLocaleBetterThan returns true if c is a better locale match than o for the r configuration. +func (c *ResTableConfig) IsLocaleBetterThan(o *ResTableConfig, r *ResTableConfig) bool { + if r.Language == [2]uint8{} && r.Country == [2]uint8{} { + // The request doesn't have a locale, so no resource is better + // than the other. + return false + } + + if c.Language == [2]uint8{} && c.Country == [2]uint8{} && o.Language == [2]uint8{} && o.Country == [2]uint8{} { + // The locales parts of both resources are empty, so no one is better + // than the other. + return false + } + + if c.Language != o.Language { + // The languages of the two resources are not the same. + + // the US English resource have traditionally lived for most apps. + if r.Language == [2]uint8{'e', 'n'} { + if r.Country == [2]uint8{'U', 'S'} { + if c.Language != [2]uint8{} { + return c.Country == [2]uint8{} || c.Country == [2]uint8{'U', 'S'} + } + return !(c.Country == [2]uint8{} || c.Country == [2]uint8{'U', 'S'}) + } + } + return c.Language != [2]uint8{} + } + + if c.Country != o.Country { + return c.Country != [2]uint8{} + } + + return false +} + +// Match returns true if c can be considered a match for the parameters in settings. +func (c *ResTableConfig) Match(settings *ResTableConfig) bool { + // nil ResTableConfig always matches. + if settings == nil { + return true + } else if c == nil { + return *settings == ResTableConfig{} + } + + // match imsi + if settings.Mcc == 0 { + if c.Mcc != 0 { + return false + } + } else { + if c.Mcc != 0 && c.Mcc != settings.Mcc { + return false + } + } + if settings.Mnc == 0 { + if c.Mnc != 0 { + return false + } + } else { + if c.Mnc != 0 && c.Mnc != settings.Mnc { + return false + } + } + + // match locale + if c.Language != [2]uint8{0, 0} { + // Don't consider country and variants when deciding matches. + // If two configs differ only in their country and variant, + // they can be weeded out in the isMoreSpecificThan test. + if c.Language != settings.Language { + return false + } + + if c.Country != [2]uint8{0, 0} { + if c.Country != settings.Country { + return false + } + } + } + + // screen layout + layoutDir := c.ScreenLayout & MaskLayoutDir + setLayoutDir := settings.ScreenLayout & MaskLayoutDir + if layoutDir != 0 && layoutDir != setLayoutDir { + return false + } + + screenSize := c.ScreenLayout & MaskScreenSize + setScreenSize := settings.ScreenLayout & MaskScreenSize + if screenSize != 0 && screenSize > setScreenSize { + return false + } + + screenLong := c.ScreenLayout & MaskScreenLong + setScreenLong := settings.ScreenLayout & MaskScreenLong + if screenLong != 0 && screenLong != setScreenLong { + return false + } + + // ui mode + uiModeType := c.UIMode & MaskUIModeType + setUIModeType := settings.UIMode & MaskUIModeType + if uiModeType != 0 && uiModeType != setUIModeType { + return false + } + + uiModeNight := c.UIMode & MaskUIModeNight + setUIModeNight := settings.UIMode & MaskUIModeNight + if uiModeNight != 0 && uiModeNight != setUIModeNight { + return false + } + + // smallest screen width dp + if c.SmallestScreenWidthDp != 0 && + c.SmallestScreenWidthDp > settings.SmallestScreenWidthDp { + return false + } + + // screen size dp + if c.ScreenWidthDp != 0 && + c.ScreenWidthDp > settings.ScreenWidthDp { + return false + } + if c.ScreenHeightDp != 0 && + c.ScreenHeightDp > settings.ScreenHeightDp { + return false + } + + // screen type + if c.Orientation != 0 && c.Orientation != settings.Orientation { + return false + } + if c.Touchscreen != 0 && c.Touchscreen != settings.Touchscreen { + return false + } + + // input + if c.InputFlags != 0 { + myKeysHidden := c.InputFlags & MaskKeysHidden + oKeysHidden := settings.InputFlags & MaskKeysHidden + if myKeysHidden != 0 && myKeysHidden != oKeysHidden { + if myKeysHidden != KeysHiddenNo || oKeysHidden != KeysHiddenSoft { + return false + } + } + myNavHidden := c.InputFlags & MaskNavHidden + oNavHidden := settings.InputFlags & MaskNavHidden + if myNavHidden != 0 && myNavHidden != oNavHidden { + return false + } + } + if c.Keyboard != 0 && c.Keyboard != settings.Keyboard { + return false + } + if c.Navigation != 0 && c.Navigation != settings.Navigation { + return false + } + + // screen size + if c.ScreenWidth != 0 && + c.ScreenWidth > settings.ScreenWidth { + return false + } + if c.ScreenHeight != 0 && + c.ScreenHeight > settings.ScreenHeight { + return false + } + + // version + if settings.SDKVersion != 0 && c.SDKVersion != 0 && + c.SDKVersion > settings.SDKVersion { + return false + } + if settings.MinorVersion != 0 && c.MinorVersion != 0 && + c.MinorVersion != settings.MinorVersion { + return false + } + + return true +} + +// Locale returns the locale of the configuration. +func (c *ResTableConfig) Locale() string { + if c.Language[0] == 0 { + return "" + } + if c.Country[0] == 0 { + return fmt.Sprintf("%c%c", c.Language[0], c.Language[1]) + } + return fmt.Sprintf("%c%c-%c%c", c.Language[0], c.Language[1], c.Country[0], c.Country[1]) +} diff --git a/vendor/github.com/shogo82148/androidbinary/xml.go b/vendor/github.com/shogo82148/androidbinary/xml.go new file mode 100644 index 0000000..f255bfa --- /dev/null +++ b/vendor/github.com/shogo82148/androidbinary/xml.go @@ -0,0 +1,259 @@ +package androidbinary + +import ( + "bytes" + "encoding/binary" + "encoding/xml" + "fmt" + "io" +) + +// XMLFile is an XML file expressed in binary format. +type XMLFile struct { + stringPool *ResStringPool + resourceMap []uint32 + notPrecessedNS map[ResStringPoolRef]ResStringPoolRef + namespaces map[ResStringPoolRef]ResStringPoolRef + xmlBuffer bytes.Buffer +} + +// ResXMLTreeNode is basic XML tree node. +type ResXMLTreeNode struct { + Header ResChunkHeader + LineNumber uint32 + Comment ResStringPoolRef +} + +// ResXMLTreeNamespaceExt is extended XML tree node for namespace start/end nodes. +type ResXMLTreeNamespaceExt struct { + Prefix ResStringPoolRef + URI ResStringPoolRef +} + +// ResXMLTreeAttrExt is extended XML tree node for start tags -- includes attribute. +type ResXMLTreeAttrExt struct { + NS ResStringPoolRef + Name ResStringPoolRef + AttributeStart uint16 + AttributeSize uint16 + AttributeCount uint16 + IDIndex uint16 + ClassIndex uint16 + StyleIndex uint16 +} + +// ResXMLTreeAttribute is an attribute of start tags. +type ResXMLTreeAttribute struct { + NS ResStringPoolRef + Name ResStringPoolRef + RawValue ResStringPoolRef + TypedValue ResValue +} + +// ResXMLTreeEndElementExt is extended XML tree node for element start/end nodes. +type ResXMLTreeEndElementExt struct { + NS ResStringPoolRef + Name ResStringPoolRef +} + +// NewXMLFile returns a new XMLFile. +func NewXMLFile(r io.ReaderAt) (*XMLFile, error) { + f := new(XMLFile) + sr := io.NewSectionReader(r, 0, 1<<63-1) + + fmt.Fprintf(&f.xmlBuffer, xml.Header) + + header := new(ResChunkHeader) + if err := binary.Read(sr, binary.LittleEndian, header); err != nil { + return nil, err + } + offset := int64(header.HeaderSize) + for offset < int64(header.Size) { + chunkHeader, err := f.readChunk(r, offset) + if err != nil { + return nil, err + } + offset += int64(chunkHeader.Size) + } + return f, nil +} + +// Reader returns a reader of XML file expressed in text format. +func (f *XMLFile) Reader() *bytes.Reader { + return bytes.NewReader(f.xmlBuffer.Bytes()) +} + +func (f *XMLFile) readChunk(r io.ReaderAt, offset int64) (*ResChunkHeader, error) { + sr := io.NewSectionReader(r, offset, 1<<63-1-offset) + chunkHeader := &ResChunkHeader{} + if _, err := sr.Seek(0, seekStart); err != nil { + return nil, err + } + if err := binary.Read(sr, binary.LittleEndian, chunkHeader); err != nil { + return nil, err + } + + var err error + if _, err := sr.Seek(0, seekStart); err != nil { + return nil, err + } + switch chunkHeader.Type { + case ResStringPoolChunkType: + f.stringPool, err = readStringPool(sr) + case ResXMLStartNamespaceType: + err = f.readStartNamespace(sr) + case ResXMLEndNamespaceType: + err = f.readEndNamespace(sr) + case ResXMLStartElementType: + err = f.readStartElement(sr) + case ResXMLEndElementType: + err = f.readEndElement(sr) + } + if err != nil { + return nil, err + } + + return chunkHeader, nil +} + +// GetString returns a string referenced by ref. +func (f *XMLFile) GetString(ref ResStringPoolRef) string { + return f.stringPool.GetString(ref) +} + +func (f *XMLFile) readStartNamespace(sr *io.SectionReader) error { + header := new(ResXMLTreeNode) + if err := binary.Read(sr, binary.LittleEndian, header); err != nil { + return err + } + + if _, err := sr.Seek(int64(header.Header.HeaderSize), seekStart); err != nil { + return err + } + namespace := new(ResXMLTreeNamespaceExt) + if err := binary.Read(sr, binary.LittleEndian, namespace); err != nil { + return err + } + + if f.notPrecessedNS == nil { + f.notPrecessedNS = make(map[ResStringPoolRef]ResStringPoolRef) + } + f.notPrecessedNS[namespace.URI] = namespace.Prefix + + if f.namespaces == nil { + f.namespaces = make(map[ResStringPoolRef]ResStringPoolRef) + } + f.namespaces[namespace.URI] = namespace.Prefix + + return nil +} + +func (f *XMLFile) readEndNamespace(sr *io.SectionReader) error { + header := new(ResXMLTreeNode) + if err := binary.Read(sr, binary.LittleEndian, header); err != nil { + return err + } + + if _, err := sr.Seek(int64(header.Header.HeaderSize), seekStart); err != nil { + return err + } + namespace := new(ResXMLTreeNamespaceExt) + if err := binary.Read(sr, binary.LittleEndian, namespace); err != nil { + return err + } + delete(f.namespaces, namespace.URI) + return nil +} + +func (f *XMLFile) addNamespacePrefix(ns, name ResStringPoolRef) string { + if ns != NilResStringPoolRef { + prefix := f.GetString(f.namespaces[ns]) + return fmt.Sprintf("%s:%s", prefix, f.GetString(name)) + } + return f.GetString(name) +} + +func (f *XMLFile) readStartElement(sr *io.SectionReader) error { + header := new(ResXMLTreeNode) + if err := binary.Read(sr, binary.LittleEndian, header); err != nil { + return err + } + + if _, err := sr.Seek(int64(header.Header.HeaderSize), seekStart); err != nil { + return err + } + ext := new(ResXMLTreeAttrExt) + if err := binary.Read(sr, binary.LittleEndian, ext); err != nil { + return nil + } + + fmt.Fprintf(&f.xmlBuffer, "<%s", f.addNamespacePrefix(ext.NS, ext.Name)) + + // output XML namespaces + if f.notPrecessedNS != nil { + for uri, prefix := range f.notPrecessedNS { + fmt.Fprintf(&f.xmlBuffer, " xmlns:%s=\"", f.GetString(prefix)) + xml.Escape(&f.xmlBuffer, []byte(f.GetString(uri))) + fmt.Fprint(&f.xmlBuffer, "\"") + } + f.notPrecessedNS = nil + } + + // process attributes + offset := int64(ext.AttributeStart + header.Header.HeaderSize) + for i := 0; i < int(ext.AttributeCount); i++ { + if _, err := sr.Seek(offset, seekStart); err != nil { + return err + } + attr := new(ResXMLTreeAttribute) + binary.Read(sr, binary.LittleEndian, attr) + + var value string + if attr.RawValue != NilResStringPoolRef { + value = f.GetString(attr.RawValue) + } else { + data := attr.TypedValue.Data + switch attr.TypedValue.DataType { + case TypeNull: + value = "" + case TypeReference: + value = fmt.Sprintf("@0x%08X", data) + case TypeIntDec: + value = fmt.Sprintf("%d", data) + case TypeIntHex: + value = fmt.Sprintf("0x%08X", data) + case TypeIntBoolean: + if data != 0 { + value = "true" + } else { + value = "false" + } + default: + value = fmt.Sprintf("@0x%08X", data) + } + } + + fmt.Fprintf(&f.xmlBuffer, " %s=\"", f.addNamespacePrefix(attr.NS, attr.Name)) + xml.Escape(&f.xmlBuffer, []byte(value)) + fmt.Fprint(&f.xmlBuffer, "\"") + offset += int64(ext.AttributeSize) + } + fmt.Fprint(&f.xmlBuffer, ">") + return nil +} + +func (f *XMLFile) readEndElement(sr *io.SectionReader) error { + header := new(ResXMLTreeNode) + if err := binary.Read(sr, binary.LittleEndian, header); err != nil { + return err + } + if _, err := sr.Seek(int64(header.Header.HeaderSize), seekStart); err != nil { + return err + } + ext := new(ResXMLTreeEndElementExt) + if err := binary.Read(sr, binary.LittleEndian, ext); err != nil { + return err + } + fmt.Fprintf(&f.xmlBuffer, "", f.addNamespacePrefix(ext.NS, ext.Name)) + return nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json new file mode 100644 index 0000000..cbbee02 --- /dev/null +++ b/vendor/vendor.json @@ -0,0 +1,37 @@ +{ + "comment": "", + "ignore": "test", + "package": [ + { + "checksumSHA1": "oYHVIFpLyGN6pVjljblPR+LS30I=", + "path": "github.com/DHowett/go-plist", + "revision": "84d08b7bddb35583bb3153b811c6cc52bb306f52", + "revisionTime": "2018-02-14T08:12:42Z" + }, + { + "checksumSHA1": "GAMb5uqJYjhZygbNxw9N5EIHvmw=", + "path": "github.com/andrianbdn/iospng", + "revision": "b54a5310f03ea9331e7f156d6764c14ffceee869", + "revisionTime": "2017-04-23T15:16:11Z" + }, + { + "checksumSHA1": "ljd3FhYRJ91cLZz3wsH9BQQ2JbA=", + "path": "github.com/pkg/errors", + "revision": "816c9085562cd7ee03e7f8188a1cfd942858cded", + "revisionTime": "2018-03-11T21:45:15Z" + }, + { + "checksumSHA1": "CkhNiFyfIGR9ctRM4rEexItcblU=", + "path": "github.com/shogo82148/androidbinary", + "revision": "f2570763f91ec28d97aa329a78bfb2b93eb41822", + "revisionTime": "2017-11-25T03:40:40Z" + }, + { + "checksumSHA1": "RXqSQ/p78DgPX/UxRF8fjJPcMdg=", + "path": "github.com/shogo82148/androidbinary/apk", + "revision": "f2570763f91ec28d97aa329a78bfb2b93eb41822", + "revisionTime": "2017-11-25T03:40:40Z" + } + ], + "rootPath": "github.com/phinexdaz/ipapk" +} From 1065fda33cc6dea2655c6abff798058ad77800cd Mon Sep 17 00:00:00 2001 From: phinexdaz Date: Wed, 2 May 2018 14:11:37 +0800 Subject: [PATCH 6/7] throws `ErrNoIcon` --- parser.go | 13 ++++++++----- parser_test.go | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/parser.go b/parser.go index 0b64caf..652c844 100644 --- a/parser.go +++ b/parser.go @@ -19,7 +19,10 @@ import ( "github.com/shogo82148/androidbinary/apk" ) -var reInfoPlist = regexp.MustCompile(`Payload/[^/]+/Info\.plist`) +var ( + reInfoPlist = regexp.MustCompile(`Payload/[^/]+/Info\.plist`) + ErrNoIcon = errors.New("icon not found") +) const ( iosExt = ".ipa" @@ -127,7 +130,7 @@ func parseAndroidManifest(xmlFile *zip.File) (*androidManifest, error) { func parseApkFile(xmlFile *zip.File) (*AppInfo, error) { if xmlFile == nil { - return nil, errors.New("AndroidManifest.xml is not found") + return nil, errors.New("AndroidManifest.xml not found") } manifest, err := parseAndroidManifest(xmlFile) @@ -154,7 +157,7 @@ func parseApkIconAndLabel(name string) (image.Image, string, error) { Density: 720, }) if icon == nil { - return nil, "", errors.New("Icon is not found") + return nil, "", ErrNoIcon } label, _ := pkg.Label(nil) @@ -164,7 +167,7 @@ func parseApkIconAndLabel(name string) (image.Image, string, error) { func parseIpaFile(plistFile *zip.File) (*AppInfo, error) { if plistFile == nil { - return nil, errors.New("info.plist is not found") + return nil, errors.New("info.plist not found") } rc, err := plistFile.Open() @@ -199,7 +202,7 @@ func parseIpaFile(plistFile *zip.File) (*AppInfo, error) { func parseIpaIcon(iconFile *zip.File) (image.Image, error) { if iconFile == nil { - return nil, errors.New("Icon is not found") + return nil, ErrNoIcon } rc, err := iconFile.Open() diff --git a/parser_test.go b/parser_test.go index 547f3ac..15cbf9d 100644 --- a/parser_test.go +++ b/parser_test.go @@ -146,7 +146,7 @@ func TestParseIpaIcon(t *testing.T) { break } } - if _, err := parseIpaIcon(iconFile); err.Error() != "Icon is not found" { - t.Errorf("got %v want %v", err, "Icon is not found") + if _, err := parseIpaIcon(iconFile); err != ErrNoIcon { + t.Errorf("got %v want %v", err, ErrNoIcon) } } From 91f3861dffcf4953ade1f6814dc909817a94dfce Mon Sep 17 00:00:00 2001 From: phinexdaz <27595954@qq.com> Date: Fri, 6 Jul 2018 22:28:10 +0800 Subject: [PATCH 7/7] update vendor --- vendor/github.com/andrianbdn/iospng/iospng.go | 92 +++++++++++++------ vendor/vendor.json | 6 +- 2 files changed, 68 insertions(+), 30 deletions(-) diff --git a/vendor/github.com/andrianbdn/iospng/iospng.go b/vendor/github.com/andrianbdn/iospng/iospng.go index 75bcdf2..a79dbe4 100644 --- a/vendor/github.com/andrianbdn/iospng/iospng.go +++ b/vendor/github.com/andrianbdn/iospng/iospng.go @@ -1,32 +1,30 @@ package iospng import ( - "io" "bytes" + "compress/zlib" "encoding/binary" + "errors" "hash/crc32" - "compress/zlib" + "io" "io/ioutil" - "errors" ) var ( - ErrPngHeader = errors.New("Not a Png"); + ErrPngHeader = errors.New("Not a Png") ErrImageData = errors.New("Unexpected amount of image data") ) - type pngChunk struct { chunkLength, chunkCRC uint32 - chunkType, chunkData []byte + chunkType, chunkData []byte } func decodePngData(data []byte) ([]byte, error) { - var zbuf bytes.Buffer - zbuf.Write([]byte{0x78, 0x1}) // looks like a good zlib header + zbuf.Write([]byte{0x78, 0x1}) // looks like a good zlib header zbuf.Write(data) - zbuf.Write([]byte{0,0,0,0}) // don't know CRC, will get zlib.ErrChecksum + zbuf.Write([]byte{0, 0, 0, 0}) // don't know CRC, will get zlib.ErrChecksum reader, err := zlib.NewReader(&zbuf) if err != nil { @@ -99,17 +97,13 @@ func (p *pngChunk) is(kind string) bool { return string(p.chunkType) == kind } -func rawImageFix(w, h int, raw []byte) error { - if len(raw) != w*h*4 + h { - return ErrImageData - } - +func unsafeImageFix(w, h int, raw []byte) { for y := 0; y < h; y++ { for x := 0; x < w; x++ { // we expect this PNG data // to be 4 bytes per pixel // 1st byte in each row is filter - row := y*w*4 + y; + row := y*w*4 + y col := x*4 + 1 b := raw[row+col+0] @@ -128,7 +122,51 @@ func rawImageFix(w, h int, raw []byte) error { } } - return nil +} + +type interlacedAdam7 struct { + xF, yF, xO, yO int +} + +var adam7factoroffset = []interlacedAdam7{ + {8, 8, 0, 0}, + {8, 8, 4, 0}, + {4, 8, 0, 4}, + {4, 4, 2, 0}, + {2, 4, 0, 2}, + {2, 2, 1, 0}, + {1, 2, 0, 1}, +} + +func rawImageFix(w, h int, interlaced bool, raw []byte) error { + if interlaced { + + total := 0 + for pass := 0; pass < 7; pass++ { + p := adam7factoroffset[pass] + + wp := (w - p.xO + p.xF - 1) / p.xF + hp := (h - p.yO + p.yF - 1) / p.yF + psize := wp*hp*4 + hp + + if total+psize > len(raw) { + return ErrImageData + } + + unsafeImageFix(wp, hp, raw[total:]) + total = total + psize + } + + return nil + + } else { + if len(raw) != w*h*4+h { + return ErrImageData + } + + unsafeImageFix(w, h, raw) + return nil + } } // This function actually does everything: @@ -139,7 +177,7 @@ func rawImageFix(w, h int, raw []byte) error { func PngRevertOptimization(reader io.Reader, writer io.Writer) error { header := make([]byte, 8) if _, err := io.ReadFull(reader, header); err != nil { - return err + return errors.New("Read error" + err.Error()) } if bytes.Compare([]byte("\x89PNG\r\n\x1a\n"), header) != 0 { @@ -148,7 +186,8 @@ func PngRevertOptimization(reader io.Reader, writer io.Writer) error { writer.Write(header) - var w, h int; + var interlaced bool + var w, h int var datbuf bytes.Buffer optimized := false @@ -158,35 +197,31 @@ func PngRevertOptimization(reader io.Reader, writer io.Writer) error { return err } - switch { case chunk.is("IHDR"): w = int(binary.BigEndian.Uint32(chunk.chunkData[:4])) h = int(binary.BigEndian.Uint32(chunk.chunkData[4:8])) - + interlaced = chunk.chunkData[12] == 1 case chunk.is("CgBI"): optimized = true - continue; + continue case chunk.is("IDAT"): if optimized { datbuf.Write(chunk.chunkData) - continue; + continue } - case chunk.is("IEND"): if optimized { - raw, err := decodePngData(datbuf.Bytes()) if err != nil { return err } - - if err = rawImageFix(w, h, raw); err != nil { + if err = rawImageFix(w, h, interlaced, raw); err != nil { return err } @@ -204,11 +239,14 @@ func PngRevertOptimization(reader io.Reader, writer io.Writer) error { err = chunk.write(writer, true) return nil + } else { + return chunk.write(writer, false) } + } if err := chunk.write(writer, false); err != nil { - return nil + return err } } diff --git a/vendor/vendor.json b/vendor/vendor.json index cbbee02..cda4acc 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -9,10 +9,10 @@ "revisionTime": "2018-02-14T08:12:42Z" }, { - "checksumSHA1": "GAMb5uqJYjhZygbNxw9N5EIHvmw=", + "checksumSHA1": "dwoXLBBJwlFDbqbYwSGcaQZvEYU=", "path": "github.com/andrianbdn/iospng", - "revision": "b54a5310f03ea9331e7f156d6764c14ffceee869", - "revisionTime": "2017-04-23T15:16:11Z" + "revision": "eb5ebcdb1909fde8a38cac3df227c5717f7577df", + "revisionTime": "2018-06-29T16:15:40Z" }, { "checksumSHA1": "ljd3FhYRJ91cLZz3wsH9BQQ2JbA=",