go-plist-master/0000755000000000000000000000000012737755636012630 5ustar rootrootgo-plist-master/text_test.go0000644000000000000000000000252212737755636015203 0ustar rootrootpackage plist import ( "bytes" "io/ioutil" "testing" ) func BenchmarkOpenStepGenerate(b *testing.B) { for i := 0; i < b.N; i++ { d := newTextPlistGenerator(ioutil.Discard, OpenStepFormat) d.generateDocument(plistValueTree) } } func BenchmarkOpenStepParse(b *testing.B) { buf := bytes.NewReader([]byte(plistValueTreeAsOpenStep)) b.ResetTimer() for i := 0; i < b.N; i++ { b.StartTimer() d := newTextPlistParser(buf) d.parseDocument() b.StopTimer() buf.Seek(0, 0) } } func BenchmarkGNUStepParse(b *testing.B) { buf := bytes.NewReader([]byte(plistValueTreeAsGNUStep)) b.ResetTimer() for i := 0; i < b.N; i++ { b.StartTimer() d := newTextPlistParser(buf) d.parseDocument() b.StopTimer() buf.Seek(0, 0) } } func TestTextCommentDecode(t *testing.T) { var testData = `{ A=1 /* A is 1 because it is the first letter */; B=2; // B is 2 because comment-to-end-of-line. C=3; S = /not/a/comment/; S2 = /not*a/*comm*en/t; }` type D struct { A, B, C int S string S2 string } actual := D{1, 2, 3, "/not/a/comment/", "/not*a/*comm*en/t"} var parsed D buf := bytes.NewReader([]byte(testData)) decoder := NewDecoder(buf) err := decoder.Decode(&parsed) if err != nil { t.Error(err.Error()) } if actual != parsed { t.Logf("Expected: %#v", actual) t.Logf("Received: %#v", parsed) t.Fail() } } go-plist-master/encode_test.go0000644000000000000000000001237412737755636015462 0ustar rootrootpackage plist import ( "bytes" "fmt" "testing" ) func BenchmarkXMLEncode(b *testing.B) { for i := 0; i < b.N; i++ { NewEncoder(&bytes.Buffer{}).Encode(plistValueTreeRawData) } } func BenchmarkBplistEncode(b *testing.B) { for i := 0; i < b.N; i++ { NewBinaryEncoder(&bytes.Buffer{}).Encode(plistValueTreeRawData) } } func BenchmarkOpenStepEncode(b *testing.B) { for i := 0; i < b.N; i++ { NewEncoderForFormat(&bytes.Buffer{}, OpenStepFormat).Encode(plistValueTreeRawData) } } func TestEncode(t *testing.T) { var failed bool for _, test := range tests { failed = false t.Logf("Testing Encode (%s)", test.Name) // A test that should render no output! errors := make(map[int]error) if test.ShouldFail && len(test.Expected) == 0 { _, err := Marshal(test.Data, XMLFormat) failed = failed || (test.ShouldFail && err == nil) } results := make(map[int][]byte) for fmt, dat := range test.Expected { results[fmt], errors[fmt] = Marshal(test.Data, fmt) failed = failed || (test.ShouldFail && errors[fmt] == nil) failed = failed || !bytes.Equal(dat, results[fmt]) } if failed { t.Logf("Value: %#v", test.Data) if test.ShouldFail { t.Logf("Expected: Error") } else { printype := "%s" for fmt, dat := range test.Expected { if fmt == BinaryFormat { printype = "%+v" } else { printype = "%s" } t.Logf("Expected %s: "+printype+"\n", FormatNames[fmt], dat) } } printype := "%s" for fmt, dat := range results { if fmt == BinaryFormat { printype = "%+v" } else { printype = "%s" } t.Logf("Received %s: "+printype+"\n", FormatNames[fmt], dat) } for fmt, err := range errors { if err != nil { t.Logf("Error %s: %v\n", FormatNames[fmt], err) } } t.Log("FAILED") t.Fail() } } } func ExampleEncoder_Encode() { type sparseBundleHeader struct { InfoDictionaryVersion string `plist:"CFBundleInfoDictionaryVersion"` BandSize uint64 `plist:"band-size"` BackingStoreVersion int `plist:"bundle-backingstore-version"` DiskImageBundleType string `plist:"diskimage-bundle-type"` Size uint64 `plist:"size"` } data := &sparseBundleHeader{ InfoDictionaryVersion: "6.0", BandSize: 8388608, Size: 4 * 1048576 * 1024 * 1024, DiskImageBundleType: "com.apple.diskimage.sparsebundle", BackingStoreVersion: 1, } buf := &bytes.Buffer{} encoder := NewEncoder(buf) err := encoder.Encode(data) if err != nil { fmt.Println(err) } fmt.Println(buf.String()) // Output: // // CFBundleInfoDictionaryVersion6.0band-size8388608bundle-backingstore-version1diskimage-bundle-typecom.apple.diskimage.sparsebundlesize4398046511104 } func ExampleMarshal_xml() { type sparseBundleHeader struct { InfoDictionaryVersion string `plist:"CFBundleInfoDictionaryVersion"` BandSize uint64 `plist:"band-size"` BackingStoreVersion int `plist:"bundle-backingstore-version"` DiskImageBundleType string `plist:"diskimage-bundle-type"` Size uint64 `plist:"size"` } data := &sparseBundleHeader{ InfoDictionaryVersion: "6.0", BandSize: 8388608, Size: 4 * 1048576 * 1024 * 1024, DiskImageBundleType: "com.apple.diskimage.sparsebundle", BackingStoreVersion: 1, } plist, err := MarshalIndent(data, XMLFormat, "\t") if err != nil { fmt.Println(err) } fmt.Println(string(plist)) // Output: // // // // CFBundleInfoDictionaryVersion // 6.0 // band-size // 8388608 // bundle-backingstore-version // 1 // diskimage-bundle-type // com.apple.diskimage.sparsebundle // size // 4398046511104 // // } func ExampleMarshal_gnustep() { type sparseBundleHeader struct { InfoDictionaryVersion string `plist:"CFBundleInfoDictionaryVersion"` BandSize uint64 `plist:"band-size"` BackingStoreVersion int `plist:"bundle-backingstore-version"` DiskImageBundleType string `plist:"diskimage-bundle-type"` Size uint64 `plist:"size"` } data := &sparseBundleHeader{ InfoDictionaryVersion: "6.0", BandSize: 8388608, Size: 4 * 1048576 * 1024 * 1024, DiskImageBundleType: "com.apple.diskimage.sparsebundle", BackingStoreVersion: 1, } plist, err := MarshalIndent(data, GNUStepFormat, "\t") if err != nil { fmt.Println(err) } fmt.Println(string(plist)) // Output: { // CFBundleInfoDictionaryVersion = 6.0; // band-size = <*I8388608>; // bundle-backingstore-version = <*I1>; // diskimage-bundle-type = com.apple.diskimage.sparsebundle; // size = <*I4398046511104>; // } } go-plist-master/marshal_test.go0000644000000000000000000000146612737755636015654 0ustar rootrootpackage plist import ( "reflect" "testing" "time" ) func BenchmarkStructMarshal(b *testing.B) { for i := 0; i < b.N; i++ { e := &Encoder{} e.marshal(reflect.ValueOf(plistValueTreeRawData)) } } func BenchmarkMapMarshal(b *testing.B) { data := map[string]interface{}{ "intarray": []interface{}{ int(1), int8(8), int16(16), int32(32), int64(64), uint(2), uint8(9), uint16(17), uint32(33), uint64(65), }, "floats": []interface{}{ float32(32.0), float64(64.0), }, "booleans": []bool{ true, false, }, "strings": []string{ "Hello, ASCII", "Hello, 世界", }, "data": []byte{1, 2, 3, 4}, "date": time.Date(2013, 11, 27, 0, 34, 0, 0, time.UTC), } b.ResetTimer() for i := 0; i < b.N; i++ { e := &Encoder{} e.marshal(reflect.ValueOf(data)) } } go-plist-master/text_tables.go0000644000000000000000000000117612737755636015502 0ustar rootrootpackage plist // 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 = [4]uint64{ 0x78001385ffffffff, 0xa800000138000000, 0xffffffffffffffff, 0xffffffffffffffff, } // 7f instead of 3f in the top line: CFOldStylePlist.c says . is valid, but they quote it. var osQuotable = [4]uint64{ 0xf4007f6fffffffff, 0xf8000001f8000001, 0xffffffffffffffff, 0xffffffffffffffff, } var whitespace = [4]uint64{ 0x0000000100003f00, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, } go-plist-master/typeinfo.go0000644000000000000000000001026612737755636015021 0ustar rootrootpackage 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 } go-plist-master/unmarshal_test.go0000644000000000000000000000123612737755636016212 0ustar rootrootpackage plist import ( "reflect" "testing" "time" ) func BenchmarkStructUnmarshal(b *testing.B) { type Data struct { Intarray []uint64 `plist:"intarray"` Floats []float64 `plist:"floats"` Booleans []bool `plist:"booleans"` Strings []string `plist:"strings"` Dat []byte `plist:"data"` Date time.Time `plist:"date"` } b.ResetTimer() for i := 0; i < b.N; i++ { var xval Data d := &Decoder{} d.unmarshal(plistValueTree, reflect.ValueOf(&xval)) } } func BenchmarkInterfaceUnmarshal(b *testing.B) { for i := 0; i < b.N; i++ { var xval interface{} d := &Decoder{} d.unmarshal(plistValueTree, reflect.ValueOf(&xval)) } } go-plist-master/must.go0000644000000000000000000000137312737755636014153 0ustar rootrootpackage 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 } go-plist-master/doc.go0000644000000000000000000000053312737755636013725 0ustar rootroot// 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 go-plist-master/fuzz.go0000644000000000000000000000032712737755636014157 0ustar rootroot// +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 } go-plist-master/decode_test.go0000644000000000000000000001560112737755636015444 0ustar rootrootpackage plist import ( "bytes" "fmt" "reflect" "testing" ) func BenchmarkXMLDecode(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() var bval interface{} buf := bytes.NewReader([]byte(plistValueTreeAsXML)) b.StartTimer() decoder := NewDecoder(buf) decoder.Decode(bval) b.StopTimer() } } func BenchmarkBplistDecode(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() var bval interface{} buf := bytes.NewReader(plistValueTreeAsBplist) b.StartTimer() decoder := NewDecoder(buf) decoder.Decode(bval) b.StopTimer() } } func TestLaxDecode(t *testing.T) { var laxTestDataStringsOnlyAsXML = `{B=1;D="2013-11-27 00:34:00 +0000";I64=1;F64="3.0";U64=2;}` d := LaxTestData{} buf := bytes.NewReader([]byte(laxTestDataStringsOnlyAsXML)) decoder := NewDecoder(buf) decoder.lax = true err := decoder.Decode(&d) if err != nil { t.Error(err.Error()) } if d != laxTestData { t.Logf("Expected: %#v", laxTestData) t.Logf("Received: %#v", d) t.Fail() } } func TestIllegalLaxDecode(t *testing.T) { i := int64(0) u := uint64(0) f := float64(0) b := false plists := []struct { pl string d interface{} }{ {"abc", &i}, {"abc", &u}, {"def", &f}, {"ghi", &b}, {"jkl", []byte{0x00}}, } for _, plist := range plists { buf := bytes.NewReader([]byte(plist.pl)) decoder := NewDecoder(buf) decoder.lax = true err := decoder.Decode(plist.d) t.Logf("Error: %v", err) if err == nil { t.Error("Expected error, received nothing.") } } } func TestIllegalDecode(t *testing.T) { i := int64(0) b := false plists := []struct { pl string d interface{} }{ {"abc", &i}, {"ABC=", &i}, {"34.1", &i}, {"def", &i}, {"2010-01-01T00:00:00Z", &i}, {"0", &b}, {"0", &b}, {"a0", &b}, {"", &[1]int{1}}, } for _, plist := range plists { buf := bytes.NewReader([]byte(plist.pl)) decoder := NewDecoder(buf) err := decoder.Decode(plist.d) t.Logf("Error: %v", err) if err == nil { t.Error("Expected error, received nothing.") } } } func TestDecode(t *testing.T) { var failed bool for _, test := range tests { failed = false t.Logf("Testing Decode (%s)", test.Name) d := test.DecodeData if d == nil { d = test.Data } testData := reflect.ValueOf(d) if !testData.IsValid() || isEmptyInterface(testData) { continue } if testData.Kind() == reflect.Ptr || testData.Kind() == reflect.Interface { testData = testData.Elem() } d = testData.Interface() results := make(map[int]interface{}) errors := make(map[int]error) for fmt, dat := range test.Expected { if test.SkipDecode[fmt] { continue } val := reflect.New(testData.Type()).Interface() _, errors[fmt] = Unmarshal(dat, val) vt := reflect.ValueOf(val) if vt.Kind() == reflect.Ptr || vt.Kind() == reflect.Interface { vt = vt.Elem() val = vt.Interface() } results[fmt] = val if !reflect.DeepEqual(d, val) { failed = true } } if results[BinaryFormat] != nil && results[XMLFormat] != nil { if !reflect.DeepEqual(results[BinaryFormat], results[XMLFormat]) { t.Log("Binary and XML decoding yielded different values.") t.Log("Binary:", results[BinaryFormat]) t.Log("XML :", results[XMLFormat]) failed = true } } if failed { t.Logf("Expected: %#v\n", d) for fmt, dat := range results { t.Logf("Received %s: %#v\n", FormatNames[fmt], dat) } for fmt, err := range errors { if err != nil { t.Logf("Error %s: %v\n", FormatNames[fmt], err) } } t.Log("FAILED") t.Fail() } } } func TestInterfaceDecode(t *testing.T) { var xval interface{} buf := bytes.NewReader([]byte{98, 112, 108, 105, 115, 116, 48, 48, 214, 1, 13, 17, 21, 25, 27, 2, 14, 18, 22, 26, 28, 88, 105, 110, 116, 97, 114, 114, 97, 121, 170, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 16, 1, 16, 8, 16, 16, 16, 32, 16, 64, 16, 2, 16, 9, 16, 17, 16, 33, 16, 65, 86, 102, 108, 111, 97, 116, 115, 162, 15, 16, 34, 66, 0, 0, 0, 35, 64, 80, 0, 0, 0, 0, 0, 0, 88, 98, 111, 111, 108, 101, 97, 110, 115, 162, 19, 20, 9, 8, 87, 115, 116, 114, 105, 110, 103, 115, 162, 23, 24, 92, 72, 101, 108, 108, 111, 44, 32, 65, 83, 67, 73, 73, 105, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 44, 0, 32, 78, 22, 117, 76, 84, 100, 97, 116, 97, 68, 1, 2, 3, 4, 84, 100, 97, 116, 101, 51, 65, 184, 69, 117, 120, 0, 0, 0, 8, 21, 30, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 68, 71, 76, 85, 94, 97, 98, 99, 107, 110, 123, 142, 147, 152, 157, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166}) decoder := NewDecoder(buf) err := decoder.Decode(&xval) if err != nil { t.Log("Error:", err) t.Fail() } } func TestFormatDetection(t *testing.T) { type formatTest struct { expectedFormat int data []byte } plists := []formatTest{ {BinaryFormat, []byte{98, 112, 108, 105, 115, 116, 48, 48, 85, 72, 101, 108, 108, 111, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14}}, {XMLFormat, []byte(`<*I3>`)}, {InvalidFormat, []byte(`bplist00`)}, // Looks like a binary property list, and bplist does not have fallbacks(!) {OpenStepFormat, []byte(`(1,2,3,4,5)`)}, {OpenStepFormat, []byte(``)}, {GNUStepFormat, []byte(`(1,2,<*I3>)`)}, {OpenStepFormat, []byte{0x00}}, } for _, fmttest := range plists { fmt, err := Unmarshal(fmttest.data, nil) if fmt != fmttest.expectedFormat { t.Errorf("Wanted %s, received %s.", FormatNames[fmttest.expectedFormat], FormatNames[fmt]) } if err != nil { t.Logf("Error: %v", err) } } } func ExampleDecoder_Decode() { type sparseBundleHeader struct { InfoDictionaryVersion string `plist:"CFBundleInfoDictionaryVersion"` BandSize uint64 `plist:"band-size"` BackingStoreVersion int `plist:"bundle-backingstore-version"` DiskImageBundleType string `plist:"diskimage-bundle-type"` Size uint64 `plist:"size"` } buf := bytes.NewReader([]byte(` CFBundleInfoDictionaryVersion 6.0 band-size 8388608 bundle-backingstore-version 1 diskimage-bundle-type com.apple.diskimage.sparsebundle size 4398046511104 `)) var data sparseBundleHeader decoder := NewDecoder(buf) err := decoder.Decode(&data) if err != nil { fmt.Println(err) } fmt.Println(data) // Output: {6.0 8388608 1 com.apple.diskimage.sparsebundle 4398046511104} } go-plist-master/LICENSE0000644000000000000000000000635012737755636013641 0ustar rootrootCopyright (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. go-plist-master/xml.go0000644000000000000000000001645612737755636013773 0ustar rootrootpackage plist import ( "encoding/base64" "encoding/xml" "errors" "fmt" "io" "math" "runtime" "strings" "time" ) const xmlDOCTYPE = ` ` type xmlPlistGenerator struct { writer io.Writer xmlEncoder *xml.Encoder } func (p *xmlPlistGenerator) generateDocument(pval *plistValue) { io.WriteString(p.writer, xml.Header) io.WriteString(p.writer, xmlDOCTYPE) plistStartElement := xml.StartElement{ Name: xml.Name{ Space: "", Local: "plist", }, Attr: []xml.Attr{{ Name: xml.Name{ Space: "", Local: "version"}, Value: "1.0"}, }, } p.xmlEncoder.EncodeToken(plistStartElement) p.writePlistValue(pval) p.xmlEncoder.EncodeToken(plistStartElement.End()) p.xmlEncoder.Flush() } func (p *xmlPlistGenerator) writePlistValue(pval *plistValue) { if pval == nil { return } defer p.xmlEncoder.Flush() key := "" encodedValue := pval.value switch pval.kind { case Dictionary: startElement := xml.StartElement{Name: xml.Name{Local: "dict"}} p.xmlEncoder.EncodeToken(startElement) dict := encodedValue.(*dictionary) dict.populateArrays() for i, k := range dict.keys { p.xmlEncoder.EncodeElement(k, xml.StartElement{Name: xml.Name{Local: "key"}}) p.writePlistValue(dict.values[i]) } p.xmlEncoder.EncodeToken(startElement.End()) case Array: startElement := xml.StartElement{Name: xml.Name{Local: "array"}} p.xmlEncoder.EncodeToken(startElement) values := encodedValue.([]*plistValue) for _, v := range values { p.writePlistValue(v) } p.xmlEncoder.EncodeToken(startElement.End()) case String: key = "string" case Integer: key = "integer" if pval.value.(signedInt).signed { encodedValue = int64(pval.value.(signedInt).value) } else { encodedValue = pval.value.(signedInt).value } case Real: key = "real" encodedValue = pval.value.(sizedFloat).value switch { case math.IsInf(pval.value.(sizedFloat).value, 1): encodedValue = "inf" case math.IsInf(pval.value.(sizedFloat).value, -1): encodedValue = "-inf" case math.IsNaN(pval.value.(sizedFloat).value): encodedValue = "nan" } case Boolean: key = "false" b := pval.value.(bool) if b { key = "true" } encodedValue = "" case Data: key = "data" encodedValue = xml.CharData(base64.StdEncoding.EncodeToString(pval.value.([]byte))) case Date: key = "date" encodedValue = pval.value.(time.Time).In(time.UTC).Format(time.RFC3339) } if key != "" { err := p.xmlEncoder.EncodeElement(encodedValue, xml.StartElement{Name: xml.Name{Local: key}}) if err != nil { panic(err) } } } func (p *xmlPlistGenerator) Indent(i string) { p.xmlEncoder.Indent("", i) } func newXMLPlistGenerator(w io.Writer) *xmlPlistGenerator { mw := mustWriter{w} return &xmlPlistGenerator{mw, xml.NewEncoder(mw)} } type xmlPlistParser struct { reader io.Reader xmlDecoder *xml.Decoder whitespaceReplacer *strings.Replacer ntags int } func (p *xmlPlistParser) parseDocument() (pval *plistValue, 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) *plistValue { 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 &plistValue{String, string(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] == '-' { n := mustParseInt(string(charData), 10, 64) return &plistValue{Integer, signedInt{uint64(n), true}} } else { n := mustParseUint(string(charData), 10, 64) return &plistValue{Integer, signedInt{n, false}} } case "real": p.ntags++ err := p.xmlDecoder.DecodeElement(&charData, &element) if err != nil { panic(err) } n := mustParseFloat(string(charData), 64) return &plistValue{Real, sizedFloat{n, 64}} case "true", "false": p.ntags++ p.xmlDecoder.Skip() b := element.Name.Local == "true" return &plistValue{Boolean, 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 &plistValue{Date, 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 &plistValue{Data, bytes[:l]} case "dict": p.ntags++ var key *string var subvalues map[string]*plistValue = make(map[string]*plistValue) 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")) } subvalues[*key] = p.parseXMLElement(el) key = nil } } } return &plistValue{Dictionary, &dictionary{m: subvalues}} case "array": p.ntags++ var subvalues []*plistValue = make([]*plistValue, 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 { subvalues = append(subvalues, p.parseXMLElement(el)) } } return &plistValue{Array, subvalues} } 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} } go-plist-master/encode.go0000644000000000000000000000756012737755636014424 0ustar rootrootpackage plist import ( "bytes" "errors" "io" "reflect" "runtime" ) type generator interface { generateDocument(*plistValue) 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 } go-plist-master/plist.go0000644000000000000000000000442412737755636014316 0ustar rootrootpackage plist import ( "reflect" "sort" ) // 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 plistKind uint const ( Invalid plistKind = iota Dictionary Array String Integer Real Boolean Data Date ) var plistKindNames map[plistKind]string = map[plistKind]string{ Invalid: "invalid", Dictionary: "dictionary", Array: "array", String: "string", Integer: "integer", Real: "real", Boolean: "boolean", Data: "data", Date: "date", } type plistValue struct { kind plistKind value interface{} } type signedInt struct { value uint64 signed bool } type sizedFloat struct { value float64 bits int } type dictionary struct { count int m map[string]*plistValue keys sort.StringSlice values []*plistValue } func (d *dictionary) Len() int { return d.count } func (d *dictionary) Less(i, j int) bool { return d.keys.Less(i, j) } func (d *dictionary) Swap(i, j int) { d.keys.Swap(i, j) d.values[i], d.values[j] = d.values[j], d.values[i] } func (d *dictionary) populateArrays() { if d.count > 0 { return } l := len(d.m) d.count = l d.keys = make([]string, l) d.values = make([]*plistValue, l) i := 0 for k, v := range d.m { d.keys[i] = k d.values[i] = v i++ } sort.Sort(d) } 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 } go-plist-master/xml_test.go0000644000000000000000000000260112737755636015015 0ustar rootrootpackage plist import ( "bytes" "io/ioutil" "testing" ) func BenchmarkXMLGenerate(b *testing.B) { for i := 0; i < b.N; i++ { d := newXMLPlistGenerator(ioutil.Discard) d.generateDocument(plistValueTree) } } func BenchmarkXMLParse(b *testing.B) { buf := bytes.NewReader([]byte(plistValueTreeAsXML)) b.ResetTimer() for i := 0; i < b.N; i++ { b.StartTimer() d := newXMLPlistParser(buf) d.parseDocument() b.StopTimer() buf.Seek(0, 0) } } func TestVariousIllegalXMLPlists(t *testing.T) { plists := []string{ "helo", "helo", "helo", "helo", "helo", "*@&%#helo", "*@&%#helo", "*@&%#helo", "10", "10", "10", "10", "10", "", "", "", "", "= 0xF { marker |= 0xF } else { marker |= uint8(count) } binary.Write(p.writer, binary.BigEndian, marker) if count >= 0xF { p.writeIntTag(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 > 0xFF { 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 *dictionary) { p.writeCountedTag(bpTagDictionary, uint64(dict.count)) vals := make([]uint64, dict.count*2) cnt := dict.count for i, k := range dict.keys { keyIdx, ok := p.uniqmap[k] if !ok { panic(errors.New("failed to find key " + k + " in object map during serialization")) } vals[i] = keyIdx } for i, v := range dict.values { 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 []*plistValue) { 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}}, } } type bplistParser struct { reader io.ReadSeeker version int buf []byte objrefs map[uint64]*plistValue offtable []uint64 trailer bplistTrailer trailerOffset int64 } func (p *bplistParser) parseDocument() (pval *plistValue, 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{"binary", r.(error)} } } }() magic := make([]byte, 6) ver := make([]byte, 2) p.reader.Seek(0, 0) p.reader.Read(magic) if !bytes.Equal(magic, []byte("bplist")) { panic(invalidPlistError{"binary", errors.New("mismatched magic")}) } _, err := p.reader.Read(ver) if err != nil { panic(err) } p.version = int(mustParseInt(string(ver), 10, 0)) if p.version > 1 { panic(fmt.Errorf("unexpected version %d", p.version)) } p.objrefs = make(map[uint64]*plistValue) p.trailerOffset, err = p.reader.Seek(-32, 2) if err != nil && err != io.EOF { panic(err) } err = binary.Read(p.reader, binary.BigEndian, &p.trailer) if err != nil && err != io.EOF { panic(err) } if p.trailer.NumObjects > uint64(math.Pow(2, 8*float64(p.trailer.ObjectRefSize))) { panic(fmt.Errorf("binary property list contains more objects (%v) than its object ref size (%v bytes) can support", p.trailer.NumObjects, p.trailer.ObjectRefSize)) } if p.trailer.TopObject >= p.trailer.NumObjects { panic(fmt.Errorf("top object index %v is out of range (only %v objects exist)", p.trailer.TopObject, p.trailer.NumObjects)) } p.offtable = make([]uint64, p.trailer.NumObjects) // SEEK_SET _, err = p.reader.Seek(int64(p.trailer.OffsetTableOffset), 0) if err != nil && err != io.EOF { panic(err) } for i := uint64(0); i < p.trailer.NumObjects; i++ { off := p.readSizedInt(int(p.trailer.OffsetIntSize)) if off >= uint64(p.trailerOffset) { panic(fmt.Errorf("object %v starts beyond end of plist trailer (%v vs %v)", i, off, p.trailerOffset)) } p.offtable[i] = off } for _, off := range p.offtable { p.valueAtOffset(off) } pval = p.valueAtOffset(p.offtable[p.trailer.TopObject]) return } func (p *bplistParser) readSizedInt(nbytes int) uint64 { switch nbytes { case 1: var val uint8 binary.Read(p.reader, binary.BigEndian, &val) return uint64(val) case 2: var val uint16 binary.Read(p.reader, binary.BigEndian, &val) return uint64(val) case 4: var val uint32 binary.Read(p.reader, binary.BigEndian, &val) return uint64(val) case 8: var val uint64 binary.Read(p.reader, binary.BigEndian, &val) return uint64(val) case 16: var high, low uint64 binary.Read(p.reader, binary.BigEndian, &high) binary.Read(p.reader, binary.BigEndian, &low) // TODO: int128 support (!) return uint64(low) } panic(errors.New("illegal integer size")) } func (p *bplistParser) countForTag(tag uint8) uint64 { cnt := uint64(tag & 0x0F) if cnt == 0xF { var intTag uint8 binary.Read(p.reader, binary.BigEndian, &intTag) cnt = p.readSizedInt(1 << (intTag & 0xF)) } return cnt } func (p *bplistParser) valueAtOffset(off uint64) *plistValue { if pval, ok := p.objrefs[off]; ok { return pval } pval := p.parseTagAtOffset(int64(off)) p.objrefs[off] = pval return pval } func (p *bplistParser) parseTagAtOffset(off int64) *plistValue { var tag uint8 _, err := p.reader.Seek(off, 0) if err != nil { panic(err) } err = binary.Read(p.reader, binary.BigEndian, &tag) if err != nil { panic(err) } switch tag & 0xF0 { case bpTagNull: switch tag & 0x0F { case bpTagBoolTrue, bpTagBoolFalse: return &plistValue{Boolean, tag == bpTagBoolTrue} } return nil case bpTagInteger: val := p.readSizedInt(1 << (tag & 0xF)) return &plistValue{Integer, signedInt{val, false}} case bpTagReal: nbytes := 1 << (tag & 0x0F) switch nbytes { case 4: var val float32 binary.Read(p.reader, binary.BigEndian, &val) return &plistValue{Real, sizedFloat{float64(val), 32}} case 8: var val float64 binary.Read(p.reader, binary.BigEndian, &val) return &plistValue{Real, sizedFloat{float64(val), 64}} } panic(errors.New("illegal float size")) case bpTagDate: var val float64 binary.Read(p.reader, binary.BigEndian, &val) // 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 &plistValue{Date, time} case bpTagData: cnt := p.countForTag(tag) if int64(cnt) > p.trailerOffset-int64(off) { panic(fmt.Errorf("data at %x longer than file (%v bytes, max is %v)", off, cnt, p.trailerOffset-int64(off))) } bytes := make([]byte, cnt) binary.Read(p.reader, binary.BigEndian, bytes) return &plistValue{Data, bytes} case bpTagASCIIString, bpTagUTF16String: cnt := p.countForTag(tag) if int64(cnt) > p.trailerOffset-int64(off) { panic(fmt.Errorf("string at %x longer than file (%v bytes, max is %v)", off, cnt, p.trailerOffset-int64(off))) } if tag&0xF0 == bpTagASCIIString { bytes := make([]byte, cnt) binary.Read(p.reader, binary.BigEndian, bytes) return &plistValue{String, string(bytes)} } else { bytes := make([]uint16, cnt) binary.Read(p.reader, binary.BigEndian, bytes) runes := utf16.Decode(bytes) return &plistValue{String, string(runes)} } case bpTagUID: // Somehow different than int: low half is nbytes - 1 instead of log2(nbytes) val := p.readSizedInt(int(tag&0xF) + 1) return &plistValue{Integer, signedInt{val, false}} case bpTagDictionary: cnt := p.countForTag(tag) subvalues := make(map[string]*plistValue) indices := make([]uint64, cnt*2) for i := uint64(0); i < cnt*2; i++ { idx := p.readSizedInt(int(p.trailer.ObjectRefSize)) if idx >= p.trailer.NumObjects { panic(fmt.Errorf("dictionary contains invalid entry index %d (max %d)", idx, p.trailer.NumObjects)) } indices[i] = idx } for i := uint64(0); i < cnt; i++ { keyOffset := p.offtable[indices[i]] valueOffset := p.offtable[indices[i+cnt]] if keyOffset == uint64(off) { panic(fmt.Errorf("dictionary contains self-referential key %x (index %d)", off, i)) } if valueOffset == uint64(off) { panic(fmt.Errorf("dictionary contains self-referential value %x (index %d)", off, i)) } kval := p.valueAtOffset(keyOffset) if kval == nil || kval.kind != String { panic(fmt.Errorf("dictionary contains non-string key at index %d", i)) } key, ok := kval.value.(string) if !ok { panic(fmt.Errorf("string-type plist value contains non-string at index %d", i)) } subvalues[key] = p.valueAtOffset(valueOffset) } return &plistValue{Dictionary, &dictionary{m: subvalues}} case bpTagArray: cnt := p.countForTag(tag) arr := make([]*plistValue, cnt) indices := make([]uint64, cnt) for i := uint64(0); i < cnt; i++ { idx := p.readSizedInt(int(p.trailer.ObjectRefSize)) if idx >= p.trailer.NumObjects { panic(fmt.Errorf("array contains invalid entry index %d (max %d)", idx, p.trailer.NumObjects)) } indices[i] = idx } for i := uint64(0); i < cnt; i++ { valueOffset := p.offtable[indices[i]] if valueOffset == uint64(off) { panic(fmt.Errorf("array contains self-referential value %x (index %d)", off, i)) } arr[i] = p.valueAtOffset(valueOffset) } return &plistValue{Array, arr} } panic(fmt.Errorf("unexpected atom 0x%2.02x at offset %d", tag, off)) } func newBplistParser(r io.ReadSeeker) *bplistParser { return &bplistParser{reader: r} } go-plist-master/bplist_test.go0000644000000000000000000000536012737755636015517 0ustar rootrootpackage plist import ( "bytes" "io/ioutil" "testing" ) func BenchmarkBplistGenerate(b *testing.B) { for i := 0; i < b.N; i++ { d := newBplistGenerator(ioutil.Discard) d.generateDocument(plistValueTree) } } func BenchmarkBplistParse(b *testing.B) { buf := bytes.NewReader(plistValueTreeAsBplist) b.ResetTimer() for i := 0; i < b.N; i++ { b.StartTimer() d := newBplistParser(buf) d.parseDocument() b.StopTimer() buf.Seek(0, 0) } } func TestBplistInt128(t *testing.T) { bplist := []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0x14, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19} expected := uint64(0x090a0b0c0d0e0f10) buf := bytes.NewReader(bplist) d := newBplistParser(buf) pval, _ := d.parseDocument() if pval.kind != Integer || pval.value.(signedInt).value != expected { t.Error("Expected", expected, "received", pval.value) } } func TestVariousIllegalBplists(t *testing.T) { bplists := [][]byte{ []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0x13}, []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0x15, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19}, []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0x24, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19}, []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xFF, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19}, []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x40, 0x41}, []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x32}, []byte{0x62, 0x71, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30}, } for _, bplist := range bplists { // We don't want the fallback behaviour for our bad file tests. buf := bytes.NewReader(bplist) d := newBplistParser(buf) _, err := d.parseDocument() t.Logf("Error: %v", err) if err == nil { t.Error("Expected error, received nothing.") } } } go-plist-master/ply/0000755000000000000000000000000012737755636013434 5ustar rootrootgo-plist-master/ply/prettyprint.go0000644000000000000000000000441612737755636016374 0ustar rootrootpackage main import ( "encoding/hex" "fmt" "io" "reflect" "sort" "time" ) func PrettyPrint(w io.Writer, val interface{}) { printValue(w, val, "") } func printMap(w io.Writer, tv reflect.Value, depth string) { fmt.Fprintf(w, "{\n") ss := make(sort.StringSlice, tv.Len()) i := 0 for _, kval := range tv.MapKeys() { if kval.Kind() == reflect.Interface { kval = kval.Elem() } if kval.Kind() != reflect.String { continue } ss[i] = kval.String() i++ } sort.Sort(ss) for _, k := range ss { val := tv.MapIndex(reflect.ValueOf(k)) v := val.Interface() nd := depth + " " for i := 0; i < len(k)+2; i++ { nd += " " } fmt.Fprintf(w, " %s%s: ", depth, k) printValue(w, v, nd) } fmt.Fprintf(w, "%s}\n", depth) } func printValue(w io.Writer, val interface{}, depth string) { switch tv := val.(type) { case map[interface{}]interface{}: printMap(w, reflect.ValueOf(tv), depth) case map[string]interface{}: printMap(w, reflect.ValueOf(tv), depth) case []interface{}: fmt.Fprintf(w, "(\n") for i, v := range tv { id := fmt.Sprintf("[%d]", i) nd := depth + " " for i := 0; i < len(id)+2; i++ { nd += " " } fmt.Fprintf(w, " %s%s: ", depth, id) printValue(w, v, nd) } fmt.Fprintf(w, "%s)\n", depth) case int64, uint64, string, float32, float64, bool, time.Time: fmt.Fprintf(w, "%+v\n", tv) case uint8: fmt.Fprintf(w, "0x%2.02x\n", tv) case []byte: l := len(tv) sxl := l / 16 if l%16 > 0 { sxl++ } sxl *= 16 var buf [4]byte var off [8]byte var asc [16]byte var ol int for i := 0; i < sxl; i++ { if i%16 == 0 { if i > 0 { io.WriteString(w, depth) } buf[0] = byte(i >> 24) buf[1] = byte(i >> 16) buf[2] = byte(i >> 8) buf[3] = byte(i) hex.Encode(off[:], buf[:]) io.WriteString(w, string(off[:])+" ") } if i < l { hex.Encode(off[:], tv[i:i+1]) if tv[i] < 32 || tv[i] > 126 { asc[i%16] = '.' } else { asc[i%16] = tv[i] } } else { off[0] = ' ' off[1] = ' ' asc[i%16] = '.' } off[2] = ' ' ol = 3 if i%16 == 7 || i%16 == 15 { off[3] = ' ' ol = 4 } io.WriteString(w, string(off[:ol])) if i%16 == 15 { io.WriteString(w, "|"+string(asc[:])+"|\n") } } default: fmt.Fprintf(w, "%#v\n", val) } } go-plist-master/ply/ply.go0000644000000000000000000002233612737755636014575 0ustar rootrootpackage main import ( "bufio" "bytes" "encoding/binary" "encoding/json" "errors" "fmt" "io" "os" "path/filepath" "reflect" "strconv" "strings" "github.com/jessevdk/go-flags" "gopkg.in/yaml.v1" "howett.net/plist" ) //import "github.com/mgutz/ansi" const ( PrettyFormat = 100 + iota JSONFormat YAMLFormat RawFormat ) var nameFormatMap = map[string]int{ "x": plist.XMLFormat, "xml": plist.XMLFormat, "xml1": plist.XMLFormat, "b": plist.BinaryFormat, "bin": plist.BinaryFormat, "binary": plist.BinaryFormat, "binary1": plist.BinaryFormat, "o": plist.OpenStepFormat, "os": plist.OpenStepFormat, "openstep": plist.OpenStepFormat, "step": plist.OpenStepFormat, "g": plist.GNUStepFormat, "gs": plist.GNUStepFormat, "gnustep": plist.GNUStepFormat, "pretty": PrettyFormat, "json": JSONFormat, "yaml": YAMLFormat, "r": RawFormat, "raw": RawFormat, } var opts struct { Convert string `short:"c" long:"convert" description:"convert the property list to a new format (c=list for list)" default:"pretty" value-name:""` Keypath string `short:"k" long:"key" description:"A keypath!" default:"/" value-name:""` Output string `short:"o" long:"out" description:"output filename" default:"" value-name:""` Indent bool `short:"I" long:"indent" description:"indent indentable output formats (xml, openstep, gnustep, json)"` } func main() { parser := flags.NewParser(&opts, flags.Default) args, err := parser.Parse() if err != nil { parser.WriteHelp(os.Stderr) fmt.Fprintln(os.Stderr, err) return } if opts.Convert == "list" { formats := make([]string, len(nameFormatMap)) i := 0 for k, _ := range nameFormatMap { formats[i] = k i++ } fmt.Fprintln(os.Stderr, "Supported output formats:") fmt.Fprintln(os.Stderr, strings.Join(formats, ", ")) return } if len(args) < 1 { parser.WriteHelp(os.Stderr) return } filename := args[0] keypath := opts.Keypath if len(keypath) == 0 { c := strings.Index(filename, ":") if c > -1 { keypath = filename[c+1:] filename = filename[:c] } } file, err := os.Open(filename) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) return } var val interface{} switch strings.ToLower(filepath.Ext(filename)) { case ".json", ".yaml", ".yml": buf := &bytes.Buffer{} io.Copy(buf, file) err = yaml.Unmarshal(buf.Bytes(), &val) default: dec := plist.NewDecoder(file) err = dec.Decode(&val) } if err != nil { fmt.Fprintln(os.Stderr, err.Error()) return } file.Close() convert := strings.ToLower(opts.Convert) format, ok := nameFormatMap[convert] if !ok { fmt.Fprintf(os.Stderr, "unknown output format %s\n", convert) return } output := opts.Output newline := false var outputStream io.WriteCloser if format < PrettyFormat && output == "" { // Writing a plist, but no output filename. Save to original. output = filename } else if format >= PrettyFormat && output == "" { // Writing a non-plist, but no output filename: Stdout outputStream = os.Stdout newline = true } else if output == "-" { // - means stdout. outputStream = os.Stdout newline = true } if outputStream == nil { outfile, err := os.Create(output) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) return } outputStream = outfile } keypathContext := &KeypathWalker{} rval, err := keypathContext.WalkKeypath(reflect.ValueOf(val), keypath) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) return } val = rval.Interface() switch { case format >= 0 && format < PrettyFormat: enc := plist.NewEncoderForFormat(outputStream, format) if opts.Indent { enc.Indent("\t") } err := enc.Encode(val) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) return } case format == PrettyFormat: PrettyPrint(outputStream, rval.Interface()) case format == JSONFormat: var out []byte var err error if opts.Indent { out, err = json.MarshalIndent(val, "", "\t") } else { out, err = json.Marshal(val) } if err != nil { fmt.Fprintln(os.Stderr, err.Error()) return } outputStream.Write(out) case format == YAMLFormat: out, err := yaml.Marshal(val) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) return } outputStream.Write(out) case format == RawFormat: newline = false switch rval.Kind() { case reflect.String: outputStream.Write([]byte(val.(string))) case reflect.Slice: if rval.Elem().Kind() == reflect.Uint8 { outputStream.Write(val.([]byte)) } default: binary.Write(outputStream, binary.LittleEndian, val) } } if newline { fmt.Fprintf(outputStream, "\n") } outputStream.Close() } type KeypathWalker struct { rootVal *reflect.Value curVal reflect.Value } func (ctx *KeypathWalker) Split(data []byte, atEOF bool) (advance int, token []byte, err error) { mode, oldmode := 0, 0 depth := 0 tok, subexpr := "", "" // modes: // 0: normal string, separated by / // 1: array index (reading between []) // 2: found $, looking for ( or nothing // 3: found $(, reading subkey, looking for ) // 4: "escape"? unused as yet. if len(data) == 0 && atEOF { return 0, nil, io.EOF } each: for _, v := range data { advance++ switch { case mode == 4: // Completing an escape sequence. tok += string(v) mode = 0 continue each case mode == 0 && v == '/': if tok != "" { break each } else { continue each } case mode == 0 && v == '[': if tok != "" { // We have encountered a [ after text, we want only the text advance-- // We don't want to consume this character. break each } else { tok += string(v) mode = 1 } case mode == 1 && v == ']': mode = 0 tok += string(v) break each case mode == 0 && v == '!': if tok == "" { tok = "!" break each } else { // We have encountered a ! after text, we want the text advance-- // We don't want to consume this character. break each } case (mode == 0 || mode == 1) && v == '$': oldmode = mode mode = 2 case mode == 2: if v == '(' { mode = 3 depth++ subexpr = "" } else { // We didn't emit the $ to begin with, so we have to do it here. tok += "$" + string(v) mode = 0 } case mode == 3 && v == '(': subexpr += string(v) depth++ case mode == 3 && v == ')': depth-- if depth == 0 { newCtx := &KeypathWalker{rootVal: ctx.rootVal} subexprVal, e := newCtx.WalkKeypath(*ctx.rootVal, subexpr) if e != nil { return 0, nil, errors.New("Dynamic subexpression " + subexpr + " failed: " + e.Error()) } if subexprVal.Kind() == reflect.Interface { subexprVal = subexprVal.Elem() } s := "" if subexprVal.Kind() == reflect.String { s = subexprVal.String() } else if subexprVal.Kind() == reflect.Uint64 { s = strconv.Itoa(int(subexprVal.Uint())) } else { return 0, nil, errors.New("Dynamic subexpression " + subexpr + " evaluated to non-string/non-int.") } tok += s mode = oldmode } else { subexpr += string(v) } case mode == 3: subexpr += string(v) default: tok += string(v) } } return advance, []byte(tok), nil } func (ctx *KeypathWalker) WalkKeypath(val reflect.Value, keypath string) (reflect.Value, error) { if keypath == "" { return val, nil } if ctx.rootVal == nil { ctx.rootVal = &val } ctx.curVal = val scanner := bufio.NewScanner(strings.NewReader(keypath)) scanner.Split(ctx.Split) for scanner.Scan() { token := scanner.Text() if ctx.curVal.Kind() == reflect.Interface { ctx.curVal = ctx.curVal.Elem() } switch { case len(token) == 0: continue case token[0] == '[': // array s := token[1 : len(token)-1] if ctx.curVal.Kind() != reflect.Slice && ctx.curVal.Kind() != reflect.String { return reflect.ValueOf(nil), errors.New("keypath attempted to index non-indexable with " + s) } colon := strings.Index(s, ":") if colon > -1 { var err error var si, sj int is := s[:colon] js := s[colon+1:] if is != "" { si, err = strconv.Atoi(is) if err != nil { return reflect.ValueOf(nil), err } } if js != "" { sj, err = strconv.Atoi(js) if err != nil { return reflect.ValueOf(nil), err } } if si < 0 || sj > ctx.curVal.Len() { return reflect.ValueOf(nil), errors.New("keypath attempted to index outside of indexable with " + s) } ctx.curVal = ctx.curVal.Slice(si, sj) } else { idx, _ := strconv.Atoi(s) ctx.curVal = ctx.curVal.Index(idx) } case token[0] == '!': // subplist! if ctx.curVal.Kind() != reflect.Slice || ctx.curVal.Type().Elem().Kind() != reflect.Uint8 { return reflect.Value{}, errors.New("Attempted to subplist non-data.") } byt := ctx.curVal.Interface().([]uint8) buf := bytes.NewReader(byt) dec := plist.NewDecoder(buf) var subval interface{} dec.Decode(&subval) ctx.curVal = reflect.ValueOf(subval) default: // just a string if ctx.curVal.Kind() != reflect.Map { return reflect.ValueOf(nil), errors.New("keypath attempted to descend into non-map using key " + token) } if token != "" { ctx.curVal = ctx.curVal.MapIndex(reflect.ValueOf(token)) } } } err := scanner.Err() if err != nil { return reflect.ValueOf(nil), err } return ctx.curVal, nil } go-plist-master/ply/README.md0000644000000000000000000000715412737755636014722 0ustar rootroot# Ply Property list pretty-printer powered by `howett.net/plist`. _verb. work with (a tool, especially one requiring steady, rhythmic movements)._ ## Installation `go get github.com/DHowett/go-plist/ply` ## Usage ``` ply [OPTIONS] Application Options: -c, --convert= convert the property list to a new format (c=list for list) (pretty) -k, --key= A keypath! (/) -o, --out= output filename -I, --indent indent indentable output formats (xml, openstep, gnustep, json) Help Options: -h, --help Show this help message ``` ## Features ### Keypath evaluation ``` $ ply file.plist { x: { y: { z: 1024 } } } $ ply -k x/y/z file.plist 1024 ``` Keypaths are composed of a number of path expressions: * `/name` - dictionary key access * `[i]` - index array, string, or data * `[i:j]` - silce array, string, or data in the range `[i, j)` * `!` - parse the data value as a property list and use it as the base of evaluation for further path components * `$(subexpression)` - evaluate `subexpression` and paste its value #### Examples Given the following property list: ``` { a = { b = { c = (1, 2, 3); d = hello; }; data = <414243>; }; sub = <7b0a0974 6869733d 22612064 69637469 6f6e6172 7920696e 73696465 20616e6f 74686572 20706c69 73742122 3b7d>; hello = subexpression; } ``` ##### pretty print ``` $ ply file.plist { a: { b: { c: ( [0]: 1 [1]: 2 [2]: 3 ) d: hello } data: 00000000 41 42 43 |ABC.............| } hello: subexpression sub: 00000000 7b 0a 09 74 68 69 73 3d 22 61 20 64 69 63 74 69 |{..this="a dicti| 00000010 6f 6e 61 72 79 20 69 6e 73 69 64 65 20 61 6e 6f |onary inside ano| 00000020 74 68 65 72 20 70 6c 69 73 74 21 22 3b 7d |ther plist!";}..| } ``` ##### consecutive dictionary keys ``` $ ply file.plist -k 'a/b/d' hello ``` ##### array indexing ``` $ ply file.plist -k 'a/b/c[1]' 2 ``` ##### data hexdump ``` $ ply file.plist -k 'a/data' 00000000 41 42 43 |ABC.............| ``` ##### data and array slicing ``` $ ply file.plist -k 'a/data[2:3]' 00000000 43 |C...............| ``` ``` $ ply -k 'sub[0:10]' file.plist 00000000 7b 0a 09 74 68 69 73 3d 22 61 |{..this="a......| ``` ##### subplist parsing ``` $ ply -k 'sub!' file.plist { this: a dictionary inside another plist! } ``` ##### subplist keypath evaluation ``` $ ply -k 'sub!/this' file.plist a dictionary inside another plist! ``` ##### subexpression evaluation ``` $ ply -k '/$(/a/b/d)' file.plist subexpression ``` ### Property list conversion `-c `, or `-c list` to list them all. * Binary property list [`bplist`] * XML [`xml`] * GNUstep [`gnustep`, `gs`] * OpenStep [`openstep`, `os`] * JSON (for a subset of data types) [`json`] * YAML [`yaml`] #### Notes By default, ply will emit the most compact representation it can for a given format. The `-I` flag influences the inclusion of whitespace. Ply will overwrite the input file unless an output filename is specified with `-o `. ### Property list subsetting (and subset conversion) ``` $ ply -k '/a/b' -o file-a-b.plist -c openstep -I file.plist $ cat file-a-b.plist { c = ( 1, 2, 3, ); d = hello; } ``` #### Subplist extraction ``` $ ply -k '/sub!' -o file-sub.plist -c openstep -I file.plist $ cat file-sub.plist { this = "a dictionary inside another plist!"; } ``` go-plist-master/common_data_for_test.go0000644000000000000000000010225312737755636017350 0ustar rootrootpackage plist import ( "math" "reflect" "time" ) type TestData struct { Name string Data interface{} DecodeData interface{} Expected map[int][]byte ShouldFail bool SkipDecode map[int]bool } type SparseBundleHeader struct { InfoDictionaryVersion string `plist:"CFBundleInfoDictionaryVersion"` BandSize uint64 `plist:"band-size"` BackingStoreVersion int `plist:"bundle-backingstore-version"` DiskImageBundleType string `plist:"diskimage-bundle-type"` Size uint64 `plist:"size"` } type EmbedA struct { EmbedC EmbedB EmbedB FieldA string } type EmbedB struct { FieldB string *EmbedC } type EmbedC struct { FieldA1 string `plist:"FieldA"` FieldA2 string FieldB string FieldC string } type TextMarshalingBool struct { b bool } func (b TextMarshalingBool) MarshalText() ([]byte, error) { if b.b { return []byte("truthful"), nil } return []byte("non-factual"), nil } func (b *TextMarshalingBool) UnmarshalText(text []byte) error { if string(text) == "truthful" { b.b = true } return nil } type TextMarshalingBoolViaPointer struct { b bool } func (b *TextMarshalingBoolViaPointer) MarshalText() ([]byte, error) { if b.b { return []byte("plausible"), nil } return []byte("unimaginable"), nil } func (b *TextMarshalingBoolViaPointer) UnmarshalText(text []byte) error { if string(text) == "plausible" { b.b = true } return nil } var xmlPreamble string = ` ` var tests = []TestData{ { Name: "Nil", Data: nil, ShouldFail: true, }, { Name: "String", Data: "Hello", Expected: map[int][]byte{ OpenStepFormat: []byte(`Hello`), GNUStepFormat: []byte(`Hello`), XMLFormat: []byte(xmlPreamble + `Hello`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 85, 72, 101, 108, 108, 111, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14}, }, }, { Name: "Basic Structure", Data: struct { Name string }{ Name: "Dustin", }, Expected: map[int][]byte{ OpenStepFormat: []byte(`{Name=Dustin;}`), GNUStepFormat: []byte(`{Name=Dustin;}`), XMLFormat: []byte(xmlPreamble + `NameDustin`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 209, 1, 2, 84, 78, 97, 109, 101, 86, 68, 117, 115, 116, 105, 110, 8, 11, 16, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23}, }, }, { Name: "Basic Structure with non-exported fields", Data: struct { Name string age int }{ Name: "Dustin", age: 24, }, DecodeData: struct { Name string age int }{ Name: "Dustin", }, Expected: map[int][]byte{ OpenStepFormat: []byte(`{Name=Dustin;}`), GNUStepFormat: []byte(`{Name=Dustin;}`), XMLFormat: []byte(xmlPreamble + `NameDustin`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 209, 1, 2, 84, 78, 97, 109, 101, 86, 68, 117, 115, 116, 105, 110, 8, 11, 16, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23}, }, }, { Name: "Basic Structure with omitted fields", Data: struct { Name string Age int `plist:"-"` }{ Name: "Dustin", Age: 24, }, DecodeData: struct { Name string Age int `plist:"-"` }{ Name: "Dustin", }, Expected: map[int][]byte{ OpenStepFormat: []byte(`{Name=Dustin;}`), GNUStepFormat: []byte(`{Name=Dustin;}`), XMLFormat: []byte(xmlPreamble + `NameDustin`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 209, 1, 2, 84, 78, 97, 109, 101, 86, 68, 117, 115, 116, 105, 110, 8, 11, 16, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23}, }, }, { Name: "Basic Structure with empty omitempty fields", Data: struct { Name string Age int `plist:"age,omitempty"` Slice []int `plist:",omitempty"` Bool bool `plist:",omitempty"` Uint uint `plist:",omitempty"` Float32 float32 `plist:",omitempty"` Float64 float64 `plist:",omitempty"` Stringptr *string `plist:",omitempty"` Notempty uint `plist:",omitempty"` }{ Name: "Dustin", Notempty: 10, }, DecodeData: struct { Name string Age int `plist:"age,omitempty"` Slice []int `plist:",omitempty"` Bool bool `plist:",omitempty"` Uint uint `plist:",omitempty"` Float32 float32 `plist:",omitempty"` Float64 float64 `plist:",omitempty"` Stringptr *string `plist:",omitempty"` Notempty uint `plist:",omitempty"` }{ Name: "Dustin", Notempty: 10, }, Expected: map[int][]byte{ OpenStepFormat: []byte(`{Name=Dustin;Notempty=10;}`), GNUStepFormat: []byte(`{Name=Dustin;Notempty=<*I10>;}`), XMLFormat: []byte(xmlPreamble + `NameDustinNotempty10`), BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd2, 0x1, 0x2, 0x3, 0x4, 0x54, 0x4e, 0x61, 0x6d, 0x65, 0x58, 0x4e, 0x6f, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x56, 0x44, 0x75, 0x73, 0x74, 0x69, 0x6e, 0x10, 0xa, 0x8, 0xd, 0x12, 0x1b, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24}, }, }, { Name: "Structure with Anonymous Embeds", Data: EmbedA{ EmbedC: EmbedC{ FieldA1: "", FieldA2: "", FieldB: "A.C.B", FieldC: "A.C.C", }, EmbedB: EmbedB{ FieldB: "A.B.B", EmbedC: &EmbedC{ FieldA1: "A.B.C.A1", FieldA2: "A.B.C.A2", FieldB: "", // Shadowed by A.B.B FieldC: "A.B.C.C", }, }, FieldA: "A.A", }, Expected: map[int][]byte{ OpenStepFormat: []byte(`{EmbedB={FieldA="A.B.C.A1";FieldA2="A.B.C.A2";FieldB="A.B.B";FieldC="A.B.C.C";};FieldA="A.A";FieldA2="";FieldB="A.C.B";FieldC="A.C.C";}`), GNUStepFormat: []byte(`{EmbedB={FieldA=A.B.C.A1;FieldA2=A.B.C.A2;FieldB=A.B.B;FieldC=A.B.C.C;};FieldA=A.A;FieldA2="";FieldB=A.C.B;FieldC=A.C.C;}`), XMLFormat: []byte(xmlPreamble + `EmbedBFieldAA.B.C.A1FieldA2A.B.C.A2FieldBA.B.BFieldCA.B.C.CFieldAA.AFieldA2FieldBA.C.BFieldCA.C.C`), BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd5, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0xb, 0xc, 0xd, 0xe, 0x56, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x42, 0x56, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x57, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x32, 0x56, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x56, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x43, 0xd4, 0x2, 0x3, 0x4, 0x5, 0x7, 0x8, 0x9, 0xa, 0x58, 0x41, 0x2e, 0x42, 0x2e, 0x43, 0x2e, 0x41, 0x31, 0x58, 0x41, 0x2e, 0x42, 0x2e, 0x43, 0x2e, 0x41, 0x32, 0x55, 0x41, 0x2e, 0x42, 0x2e, 0x42, 0x57, 0x41, 0x2e, 0x42, 0x2e, 0x43, 0x2e, 0x43, 0x53, 0x41, 0x2e, 0x41, 0x50, 0x55, 0x41, 0x2e, 0x43, 0x2e, 0x42, 0x55, 0x41, 0x2e, 0x43, 0x2e, 0x43, 0x8, 0x13, 0x1a, 0x21, 0x29, 0x30, 0x37, 0x40, 0x49, 0x52, 0x58, 0x60, 0x64, 0x65, 0x6b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x71}, }, }, { Name: "Arbitrary Byte Data", Data: []byte{'h', 'e', 'l', 'l', 'o'}, Expected: map[int][]byte{ OpenStepFormat: []byte(`<68656c6c 6f>`), GNUStepFormat: []byte(`<68656c6c 6f>`), XMLFormat: []byte(xmlPreamble + `aGVsbG8=`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 69, 104, 101, 108, 108, 111, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14}, }, }, { Name: "Arbitrary Integer Slice", Data: []int{'h', 'e', 'l', 'l', 'o'}, Expected: map[int][]byte{ OpenStepFormat: []byte(`(104,101,108,108,111,)`), GNUStepFormat: []byte(`(<*I104>,<*I101>,<*I108>,<*I108>,<*I111>,)`), XMLFormat: []byte(xmlPreamble + `104101108108111`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 165, 1, 2, 3, 3, 4, 16, 104, 16, 101, 16, 108, 16, 111, 8, 14, 16, 18, 20, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22}, }, }, { Name: "Arbitrary Integer Array", Data: [3]int{'h', 'i', '!'}, Expected: map[int][]byte{ OpenStepFormat: []byte(`(104,105,33,)`), GNUStepFormat: []byte(`(<*I104>,<*I105>,<*I33>,)`), XMLFormat: []byte(xmlPreamble + `10410533`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 163, 1, 2, 3, 16, 104, 16, 105, 16, 33, 8, 12, 14, 16, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18}, }, }, { Name: "Unsigned Integers of Increasing Size", Data: []uint64{0xff, 0xfff, 0xffff, 0xfffff, 0xffffff, 0xfffffff, 0xffffffff, 0xffffffffffffffff}, Expected: map[int][]byte{ OpenStepFormat: []byte(`(255,4095,65535,1048575,16777215,268435455,4294967295,18446744073709551615,)`), GNUStepFormat: []byte(`(<*I255>,<*I4095>,<*I65535>,<*I1048575>,<*I16777215>,<*I268435455>,<*I4294967295>,<*I18446744073709551615>,)`), XMLFormat: []byte(xmlPreamble + `255409565535104857516777215268435455429496729518446744073709551615`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 168, 1, 2, 3, 4, 5, 6, 7, 8, 16, 255, 17, 15, 255, 17, 255, 255, 18, 0, 15, 255, 255, 18, 0, 255, 255, 255, 18, 15, 255, 255, 255, 18, 255, 255, 255, 255, 19, 255, 255, 255, 255, 255, 255, 255, 255, 8, 17, 19, 22, 25, 30, 35, 40, 45, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54}, }, }, { Name: "Floats of Increasing Bitness", Data: []interface{}{float32(math.MaxFloat32), float64(math.MaxFloat64)}, Expected: map[int][]byte{ OpenStepFormat: []byte(`(3.4028234663852886e+38,1.7976931348623157e+308,)`), GNUStepFormat: []byte(`(<*R3.4028234663852886e+38>,<*R1.7976931348623157e+308>,)`), XMLFormat: []byte(xmlPreamble + `3.4028234663852886e+381.7976931348623157e+308`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 162, 1, 2, 34, 127, 127, 255, 255, 35, 127, 239, 255, 255, 255, 255, 255, 255, 8, 11, 16, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25}, }, // We can't store varying bitness in text formats. SkipDecode: map[int]bool{XMLFormat: true, OpenStepFormat: true, GNUStepFormat: true}, }, { Name: "Boolean True", Data: true, Expected: map[int][]byte{ OpenStepFormat: []byte(`1`), GNUStepFormat: []byte(`<*BY>`), XMLFormat: []byte(xmlPreamble + ``), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 9, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9}, }, }, { Name: "Floating-Point Value", Data: 3.14159265358979323846264338327950288, Expected: map[int][]byte{ OpenStepFormat: []byte(`3.141592653589793`), GNUStepFormat: []byte(`<*R3.141592653589793>`), XMLFormat: []byte(xmlPreamble + `3.141592653589793`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 35, 64, 9, 33, 251, 84, 68, 45, 24, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17}, }, }, { Name: "Map (containing arbitrary types)", Data: map[string]interface{}{ "float": 1.0, "uint64": uint64(1), }, Expected: map[int][]byte{ OpenStepFormat: []byte(`{float=1;uint64=1;}`), GNUStepFormat: []byte(`{float=<*R1>;uint64=<*I1>;}`), XMLFormat: []byte(xmlPreamble + `float1uint641`), BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd2, 0x1, 0x2, 0x3, 0x4, 0x55, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x23, 0x3f, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x1, 0x8, 0xd, 0x13, 0x1a, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x25}, }, // Can't lax decode strings into numerics in a map (we don't know they want numbers) SkipDecode: map[int]bool{OpenStepFormat: true}, }, { Name: "Map (containing all variations of all types)", Data: interface{}(map[string]interface{}{ "intarray": []interface{}{ int(1), int8(8), int16(16), int32(32), int64(64), uint(2), uint8(9), uint16(17), uint32(33), uint64(65), }, "floats": []interface{}{ float32(32.0), float64(64.0), }, "booleans": []bool{ true, false, }, "strings": []string{ "Hello, ASCII", "Hello, 世界", }, "data": []byte{1, 2, 3, 4}, "date": time.Date(2013, 11, 27, 0, 34, 0, 0, time.UTC), }), Expected: map[int][]byte{ OpenStepFormat: []byte(`{booleans=(1,0,);data=<01020304>;date="2013-11-27 00:34:00 +0000";floats=(32,64,);intarray=(1,8,16,32,64,2,9,17,33,65,);strings=("Hello, ASCII","Hello, \U4e16\U754c",);}`), GNUStepFormat: []byte(`{booleans=(<*BY>,<*BN>,);data=<01020304>;date=<*D2013-11-27 00:34:00 +0000>;floats=(<*R32>,<*R64>,);intarray=(<*I1>,<*I8>,<*I16>,<*I32>,<*I64>,<*I2>,<*I9>,<*I17>,<*I33>,<*I65>,);strings=("Hello, ASCII","Hello, \U4e16\U754c",);}`), XMLFormat: []byte(xmlPreamble + `booleansdataAQIDBA==date2013-11-27T00:34:00Zfloats3264intarray1816326429173365stringsHello, ASCIIHello, 世界`), BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd6, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xa, 0xb, 0xc, 0xf, 0x1a, 0x58, 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x73, 0x54, 0x64, 0x61, 0x74, 0x61, 0x54, 0x64, 0x61, 0x74, 0x65, 0x56, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x73, 0x58, 0x69, 0x6e, 0x74, 0x61, 0x72, 0x72, 0x61, 0x79, 0x57, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0xa2, 0x8, 0x9, 0x9, 0x8, 0x44, 0x1, 0x2, 0x3, 0x4, 0x33, 0x41, 0xb8, 0x45, 0x75, 0x78, 0x0, 0x0, 0x0, 0xa2, 0xd, 0xe, 0x22, 0x42, 0x0, 0x0, 0x0, 0x23, 0x40, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaa, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x10, 0x1, 0x10, 0x8, 0x10, 0x10, 0x10, 0x20, 0x10, 0x40, 0x10, 0x2, 0x10, 0x9, 0x10, 0x11, 0x10, 0x21, 0x10, 0x41, 0xa2, 0x1b, 0x1c, 0x5c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x41, 0x53, 0x43, 0x49, 0x49, 0x69, 0x0, 0x48, 0x0, 0x65, 0x0, 0x6c, 0x0, 0x6c, 0x0, 0x6f, 0x0, 0x2c, 0x0, 0x20, 0x4e, 0x16, 0x75, 0x4c, 0x8, 0x15, 0x1e, 0x23, 0x28, 0x2f, 0x38, 0x40, 0x43, 0x44, 0x45, 0x4a, 0x53, 0x56, 0x5b, 0x64, 0x6f, 0x71, 0x73, 0x75, 0x77, 0x79, 0x7b, 0x7d, 0x7f, 0x81, 0x83, 0x86, 0x93, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa6}, }, SkipDecode: map[int]bool{OpenStepFormat: true, GNUStepFormat: true, XMLFormat: true, BinaryFormat: true}, }, { Name: "Map (containing nil)", Data: map[string]interface{}{ "float": 1.5, "uint64": uint64(1), "nil": nil, }, DecodeData: map[string]interface{}{ "float": 1.5, "uint64": uint64(1), }, Expected: map[int][]byte{ OpenStepFormat: []byte(`{float=1.5;uint64=1;}`), GNUStepFormat: []byte(`{float=<*R1.5>;uint64=<*I1>;}`), XMLFormat: []byte(xmlPreamble + `float1.5uint641`), BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd2, 0x1, 0x2, 0x3, 0x4, 0x55, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x23, 0x3f, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x1, 0x8, 0xd, 0x13, 0x1a, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x25}, }, // Can't lax decode strings into numerics in a map (we don't know they want numbers) SkipDecode: map[int]bool{OpenStepFormat: true}, }, { Name: "Map (integer keys) (expected to fail)", Data: map[int]string{1: "hi"}, ShouldFail: true, // No types to decode, no need to add skips }, { Name: "Pointer to structure with plist tags", Data: &SparseBundleHeader{ InfoDictionaryVersion: "6.0", BandSize: 8388608, Size: 4 * 1048576 * 1024 * 1024, DiskImageBundleType: "com.apple.diskimage.sparsebundle", BackingStoreVersion: 1, }, Expected: map[int][]byte{ OpenStepFormat: []byte(`{CFBundleInfoDictionaryVersion="6.0";"band-size"=8388608;"bundle-backingstore-version"=1;"diskimage-bundle-type"="com.apple.diskimage.sparsebundle";size=4398046511104;}`), GNUStepFormat: []byte(`{CFBundleInfoDictionaryVersion=6.0;band-size=<*I8388608>;bundle-backingstore-version=<*I1>;diskimage-bundle-type=com.apple.diskimage.sparsebundle;size=<*I4398046511104>;}`), XMLFormat: []byte(xmlPreamble + `CFBundleInfoDictionaryVersion6.0band-size8388608bundle-backingstore-version1diskimage-bundle-typecom.apple.diskimage.sparsebundlesize4398046511104`), BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd5, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0x5f, 0x10, 0x1d, 0x43, 0x46, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x44, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x72, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x59, 0x62, 0x61, 0x6e, 0x64, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x10, 0x1b, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2d, 0x62, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2d, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x10, 0x15, 0x64, 0x69, 0x73, 0x6b, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2d, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x54, 0x73, 0x69, 0x7a, 0x65, 0x53, 0x36, 0x2e, 0x30, 0x12, 0x0, 0x80, 0x0, 0x0, 0x10, 0x1, 0x5f, 0x10, 0x20, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2e, 0x73, 0x70, 0x61, 0x72, 0x73, 0x65, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x13, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x13, 0x33, 0x3d, 0x5b, 0x73, 0x78, 0x7c, 0x81, 0x83, 0xa6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaf}, }, SkipDecode: map[int]bool{OpenStepFormat: true}, }, { Name: "Array of byte arrays", Data: [][]byte{ []byte("Hello"), []byte("World"), }, Expected: map[int][]byte{ OpenStepFormat: []byte(`(<48656c6c 6f>,<576f726c 64>,)`), GNUStepFormat: []byte(`(<48656c6c 6f>,<576f726c 64>,)`), XMLFormat: []byte(xmlPreamble + `SGVsbG8=V29ybGQ=`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 162, 1, 2, 69, 72, 101, 108, 108, 111, 69, 87, 111, 114, 108, 100, 8, 11, 17, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23}, }, }, { Name: "Date", Data: time.Date(2013, 11, 27, 0, 34, 0, 0, time.UTC), Expected: map[int][]byte{ OpenStepFormat: []byte(`"2013-11-27 00:34:00 +0000"`), GNUStepFormat: []byte(`<*D2013-11-27 00:34:00 +0000>`), XMLFormat: []byte(xmlPreamble + `2013-11-27T00:34:00Z`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 51, 65, 184, 69, 117, 120, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17}, }, }, { Name: "Floating-Point NaN", Data: math.NaN(), Expected: map[int][]byte{ OpenStepFormat: []byte(`NaN`), GNUStepFormat: []byte(`<*RNaN>`), XMLFormat: []byte(xmlPreamble + `nan`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 35, 127, 248, 0, 0, 0, 0, 0, 1, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17}, }, SkipDecode: map[int]bool{OpenStepFormat: true, GNUStepFormat: true, XMLFormat: true, BinaryFormat: true}, }, { Name: "Floating-Point Infinity", Data: math.Inf(1), Expected: map[int][]byte{ OpenStepFormat: []byte(`+Inf`), GNUStepFormat: []byte(`<*R+Inf>`), XMLFormat: []byte(xmlPreamble + `inf`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 35, 127, 240, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17}, }, }, { Name: "UTF-8 string", Data: []string{"Hello, ASCII", "Hello, 世界"}, Expected: map[int][]byte{ OpenStepFormat: []byte(`("Hello, ASCII","Hello, \U4e16\U754c",)`), GNUStepFormat: []byte(`("Hello, ASCII","Hello, \U4e16\U754c",)`), XMLFormat: []byte(xmlPreamble + `Hello, ASCIIHello, 世界`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 162, 1, 2, 92, 72, 101, 108, 108, 111, 44, 32, 65, 83, 67, 73, 73, 105, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 44, 0, 32, 78, 22, 117, 76, 8, 11, 24, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43}, }, }, { Name: "An array containing more than fifteen items", Data: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, Expected: map[int][]byte{ OpenStepFormat: []byte(`(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,)`), GNUStepFormat: []byte(`(<*I1>,<*I2>,<*I3>,<*I4>,<*I5>,<*I6>,<*I7>,<*I8>,<*I9>,<*I10>,<*I11>,<*I12>,<*I13>,<*I14>,<*I15>,<*I16>,)`), XMLFormat: []byte(xmlPreamble + `12345678910111213141516`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 175, 16, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16, 1, 16, 2, 16, 3, 16, 4, 16, 5, 16, 6, 16, 7, 16, 8, 16, 9, 16, 10, 16, 11, 16, 12, 16, 13, 16, 14, 16, 15, 16, 16, 8, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59}, }, }, { Name: "TextMarshaler/TextUnmarshaler", Data: TextMarshalingBool{true}, Expected: map[int][]byte{ OpenStepFormat: []byte(`truthful`), GNUStepFormat: []byte(`truthful`), XMLFormat: []byte(xmlPreamble + `truthful`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 88, 116, 114, 117, 116, 104, 102, 117, 108, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17}, }, // We expect false here because the non-pointer version cannot mutate itself. }, { Name: "TextMarshaler/TextUnmarshaler via Pointer", Data: &TextMarshalingBoolViaPointer{false}, Expected: map[int][]byte{ OpenStepFormat: []byte(`unimaginable`), GNUStepFormat: []byte(`unimaginable`), XMLFormat: []byte(xmlPreamble + `unimaginable`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 92, 117, 110, 105, 109, 97, 103, 105, 110, 97, 98, 108, 101, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21}, }, DecodeData: TextMarshalingBoolViaPointer{false}, }, { Name: "Duplicated Values", Data: []interface{}{ "Hello", float32(32.0), float64(32.0), []byte("data"), float32(64.0), float64(64.0), uint64(100), float32(32.0), time.Date(2013, 11, 27, 0, 34, 0, 0, time.UTC), float64(32.0), float32(64.0), float64(64.0), "Hello", []byte("data"), uint64(100), time.Date(2013, 11, 27, 0, 34, 0, 0, time.UTC), }, Expected: map[int][]byte{ BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xaf, 0x10, 0x10, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x2, 0x8, 0x3, 0x5, 0x6, 0x1, 0x4, 0x7, 0x8, 0x55, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x22, 0x42, 0x0, 0x0, 0x0, 0x23, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x44, 0x64, 0x61, 0x74, 0x61, 0x22, 0x42, 0x80, 0x0, 0x0, 0x23, 0x40, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x64, 0x33, 0x41, 0xb8, 0x45, 0x75, 0x78, 0x0, 0x0, 0x0, 0x8, 0x1b, 0x21, 0x26, 0x2f, 0x34, 0x39, 0x42, 0x44, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4d}, }, }, { Name: "Funny Characters", Data: map[string]string{ "\a": "\b", "\v": "\f", "\\": "\"", "\t\r": "\n", "\u00C8": "wat", "\u0100": "hundred", }, Expected: map[int][]byte{ // Hard to encode these in a raw string ;P OpenStepFormat: []byte{0x7b, 0x22, 0x5c, 0x61, 0x22, 0x3d, 0x22, 0x5c, 0x62, 0x22, 0x3b, 0x22, 0x9, 0xd, 0x22, 0x3d, 0x22, 0xa, 0x22, 0x3b, 0x22, 0x5c, 0x76, 0x22, 0x3d, 0x22, 0x5c, 0x66, 0x22, 0x3b, 0x22, 0x5c, 0x5c, 0x22, 0x3d, 0x22, 0x5c, 0x22, 0x22, 0x3b, 0x22, 0x5c, 0x33, 0x31, 0x30, 0x22, 0x3d, 0x77, 0x61, 0x74, 0x3b, 0x22, 0x5c, 0x55, 0x30, 0x31, 0x30, 0x30, 0x22, 0x3d, 0x68, 0x75, 0x6e, 0x64, 0x72, 0x65, 0x64, 0x3b, 0x7d}, GNUStepFormat: []byte{0x7b, 0x22, 0x5c, 0x61, 0x22, 0x3d, 0x22, 0x5c, 0x62, 0x22, 0x3b, 0x22, 0x9, 0xd, 0x22, 0x3d, 0x22, 0xa, 0x22, 0x3b, 0x22, 0x5c, 0x76, 0x22, 0x3d, 0x22, 0x5c, 0x66, 0x22, 0x3b, 0x22, 0x5c, 0x5c, 0x22, 0x3d, 0x22, 0x5c, 0x22, 0x22, 0x3b, 0x22, 0x5c, 0x33, 0x31, 0x30, 0x22, 0x3d, 0x77, 0x61, 0x74, 0x3b, 0x22, 0x5c, 0x55, 0x30, 0x31, 0x30, 0x30, 0x22, 0x3d, 0x68, 0x75, 0x6e, 0x64, 0x72, 0x65, 0x64, 0x3b, 0x7d}, }, }, { Name: "Signed Integers", Data: []int64{-1, -127, -255, -32767, -65535}, Expected: map[int][]byte{ OpenStepFormat: []byte(`(-1,-127,-255,-32767,-65535,)`), GNUStepFormat: []byte(`(<*I-1>,<*I-127>,<*I-255>,<*I-32767>,<*I-65535>,)`), XMLFormat: []byte(xmlPreamble + `-1-127-255-32767-65535`), BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xa5, 0x1, 0x2, 0x3, 0x4, 0x5, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x1, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x1, 0x8, 0xe, 0x17, 0x20, 0x29, 0x32, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3b}, }, }, { Name: "A Channel", Data: make(chan int), ShouldFail: true, }, { Name: "A Function", Data: func() {}, ShouldFail: true, }, { Name: "A map with a blank key", Data: map[string]string{ "": "Hello", }, Expected: map[int][]byte{ OpenStepFormat: []byte(`{""=Hello;}`), GNUStepFormat: []byte(`{""=Hello;}`), XMLFormat: []byte(xmlPreamble + `Hello`), BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 209, 1, 2, 80, 85, 72, 101, 108, 108, 111, 8, 11, 12, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18}, }, }, } type EverythingTestData struct { Intarray []uint64 `plist:"intarray"` Floats []float64 `plist:"floats"` Booleans []bool `plist:"booleans"` Strings []string `plist:"strings"` Dat []byte `plist:"data"` Date time.Time `plist:"date"` } var plistValueTreeRawData *EverythingTestData = &EverythingTestData{ Intarray: []uint64{1, 8, 16, 32, 64, 2, 9, 17, 33, 65}, Floats: []float64{32.0, 64.0}, Booleans: []bool{true, false}, Strings: []string{"Hello, ASCII", "Hello, 世界"}, Dat: []byte{1, 2, 3, 4}, Date: time.Date(2013, 11, 27, 0, 34, 0, 0, time.UTC), } var plistValueTree *plistValue var plistValueTreeAsBplist []byte = []byte{98, 112, 108, 105, 115, 116, 48, 48, 214, 1, 13, 17, 21, 25, 27, 2, 14, 18, 22, 26, 28, 88, 105, 110, 116, 97, 114, 114, 97, 121, 170, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 16, 1, 16, 8, 16, 16, 16, 32, 16, 64, 16, 2, 16, 9, 16, 17, 16, 33, 16, 65, 86, 102, 108, 111, 97, 116, 115, 162, 15, 16, 34, 66, 0, 0, 0, 35, 64, 80, 0, 0, 0, 0, 0, 0, 88, 98, 111, 111, 108, 101, 97, 110, 115, 162, 19, 20, 9, 8, 87, 115, 116, 114, 105, 110, 103, 115, 162, 23, 24, 92, 72, 101, 108, 108, 111, 44, 32, 65, 83, 67, 73, 73, 105, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 44, 0, 32, 78, 22, 117, 76, 84, 100, 97, 116, 97, 68, 1, 2, 3, 4, 84, 100, 97, 116, 101, 51, 65, 184, 69, 117, 120, 0, 0, 0, 8, 21, 30, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 68, 71, 76, 85, 94, 97, 98, 99, 107, 110, 123, 142, 147, 152, 157, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166} var plistValueTreeAsXML string = xmlPreamble + `intarray1816326429173365floats3264booleansstringsHello, ASCIIHello, 世界dataAQIDBA==date2013-11-27T00:34:00Z` var plistValueTreeAsOpenStep string = `{booleans=(1,0,);data=<01020304>;date="2013-11-27 00:34:00 +0000";floats=(32,64,);intarray=(1,8,16,32,64,2,9,17,33,65,);strings=("Hello, ASCII","Hello, \U4e16\U754c",);}` var plistValueTreeAsGNUStep string = `{booleans=(<*BY>,<*BN>,);data=<01020304>;date=<*D2013-11-27 00:34:00 +0000>;floats=(<*R32>,<*R64>,);intarray=(<*I1>,<*I8>,<*I16>,<*I32>,<*I64>,<*I2>,<*I9>,<*I17>,<*I33>,<*I65>,);strings=("Hello, ASCII","Hello, \U4e16\U754c",);}` type LaxTestData struct { I64 int64 U64 uint64 F64 float64 B bool D time.Time } var laxTestData = LaxTestData{1, 2, 3.0, true, time.Date(2013, 11, 27, 0, 34, 0, 0, time.UTC)} func setupPlistValues() { plistValueTree = &plistValue{ Dictionary, &dictionary{m: map[string]*plistValue{ "intarray": &plistValue{Array, []*plistValue{ &plistValue{Integer, signedInt{uint64(1), false}}, &plistValue{Integer, signedInt{uint64(8), false}}, &plistValue{Integer, signedInt{uint64(16), false}}, &plistValue{Integer, signedInt{uint64(32), false}}, &plistValue{Integer, signedInt{uint64(64), false}}, &plistValue{Integer, signedInt{uint64(2), false}}, &plistValue{Integer, signedInt{uint64(8), false}}, &plistValue{Integer, signedInt{uint64(17), false}}, &plistValue{Integer, signedInt{uint64(33), false}}, &plistValue{Integer, signedInt{uint64(65), false}}, }}, "floats": &plistValue{Array, []*plistValue{ &plistValue{Real, sizedFloat{float64(32.0), 32}}, &plistValue{Real, sizedFloat{float64(64.0), 64}}, }}, "booleans": &plistValue{Array, []*plistValue{ &plistValue{Boolean, true}, &plistValue{Boolean, false}, }}, "strings": &plistValue{Array, []*plistValue{ &plistValue{String, "Hello, ASCII"}, &plistValue{String, "Hello, 世界"}, }}, "data": &plistValue{Data, []byte{1, 2, 3, 4}}, "date": &plistValue{Date, time.Date(2013, 11, 27, 0, 34, 0, 0, time.UTC)}, }}, } } func init() { setupPlistValues() // Pre-warm the type info struct to remove it from benchmarking getTypeInfo(reflect.ValueOf(plistValueTreeRawData).Type()) } go-plist-master/marshal.go0000644000000000000000000001032712737755636014611 0ustar rootrootpackage 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 ( textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() timeType = reflect.TypeOf((*time.Time)(nil)).Elem() ) func (p *Encoder) marshalTextInterface(marshalable encoding.TextMarshaler) *plistValue { s, err := marshalable.MarshalText() if err != nil { panic(err) } return &plistValue{String, string(s)} } func (p *Encoder) marshalStruct(typ reflect.Type, val reflect.Value) *plistValue { tinfo, _ := getTypeInfo(typ) dict := &dictionary{ m: make(map[string]*plistValue, len(tinfo.fields)), } for _, finfo := range tinfo.fields { value := finfo.value(val) if !value.IsValid() || finfo.omitEmpty && isEmptyValue(value) { continue } dict.m[finfo.name] = p.marshal(value) } return &plistValue{Dictionary, dict} } func (p *Encoder) marshalTime(val reflect.Value) *plistValue { time := val.Interface().(time.Time) return &plistValue{Date, time} } func (p *Encoder) marshal(val reflect.Value) *plistValue { if !val.IsValid() { return nil } // 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 val.CanInterface() && val.Type().Implements(textMarshalerType) { return p.marshalTextInterface(val.Interface().(encoding.TextMarshaler)) } if val.CanAddr() { pv := val.Addr() if pv.CanInterface() && pv.Type().Implements(textMarshalerType) { return p.marshalTextInterface(pv.Interface().(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 val.Kind() == reflect.Struct { return p.marshalStruct(typ, val) } switch val.Kind() { case reflect.String: return &plistValue{String, val.String()} case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return &plistValue{Integer, signedInt{uint64(val.Int()), true}} case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return &plistValue{Integer, signedInt{uint64(val.Uint()), false}} case reflect.Float32, reflect.Float64: return &plistValue{Real, sizedFloat{val.Float(), val.Type().Bits()}} case reflect.Bool: return &plistValue{Boolean, 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 &plistValue{Data, bytes} } else { subvalues := make([]*plistValue, val.Len()) for idx, length := 0, val.Len(); idx < length; idx++ { if subpval := p.marshal(val.Index(idx)); subpval != nil { subvalues[idx] = subpval } } return &plistValue{Array, subvalues} } case reflect.Map: if typ.Key().Kind() != reflect.String { panic(&unknownTypeError{typ}) } l := val.Len() dict := &dictionary{ m: make(map[string]*plistValue, l), } for _, keyv := range val.MapKeys() { if subpval := p.marshal(val.MapIndex(keyv)); subpval != nil { dict.m[keyv.String()] = subpval } } return &plistValue{Dictionary, dict} default: panic(&unknownTypeError{typ}) } return nil } go-plist-master/invalid_bplist_test.go0000644000000000000000000002012512737755636017221 0ustar rootrootpackage plist import ( "fmt" "strings" "testing" ) var InvalidBplists []string = []string{ "bplist00\xdc\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x10\x11\x12\x13\x14\x15\x16\x19\x15#$_\x10\x11CFPlugInFactories_\x10\x12CFBundleIdentifier_\x10\x1bCFPlugInDynamicRegistration_\x10\x1dCFBundleInfoDictionaryVersion_\x10\x0fCFBundleVersion_\x10\x12CFBundleExecutable_\x10\x16CFPlugInUnloadFunction]CFPlugInTypes_\x10\x15CFBundleDocumentTypes_\x10\x1fCFPlugInDynamicRegisterFunction_\x10\x19CFBundleDevelopmentRegion\\CFBundleName\xd1\x0e\x0f_\x10$9B6E3B91-772C-4DA9-9EFC-06B51846737E_\x10\x1dMetadataImporterPluginFactory_\x10\x19com.adiumX.adiumXimporterRNOS6.0S1.0_\x10\x16AdiumSpotlightImporterP\xd1\x17\x18_\x10$8B08C4BF-415B-11D8-B3F9-0003936726FC\xa1\x0e\xa1\x1a\xd3\x1b\x1c\x1d\x1e\x1f _\x10\x10CFBundleTypeRole_\x10\x0fLSTypeIsPackage_\x10\x12LSItemContentTypesZMDImporter\t\xa2!\"_\x10\x12com.adiumx.htmllog_\x10\x11com.adiumx.xmllogWEnglish_\x10\x16AdiumSpotlightImporter\x00\b\x00J\x00h\x00\x88\x00\x9a\x00\xaf\x00\xc8\x00\xd6\x00\xee\x01\x10\x01\x019\x01<\x01c\x01\x83\x01\x9f\x01\xa2\x01\xa6\x01\xaa\x01\xc3\x01\xc7\x01\xee\x01\x01\xf2\x01\xf9\x02\f\x02\x1e\x023>\x02\x01\x00\x00\x00\x00\x00\x00\x00%\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x8c", "bplist00\xdc\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x10\x11\x12\x13\x14\x15\x16\x19\x15#$_\x10\x11CFPlugInFactories_\x10\x12CFBundleIdentifier_\x10\x1bCFPlugInDynamicRegistration_\x10\x1dCFBundleInfoDictionaryVersion_\x10\x0fCFBundleVersion_\x10\x12CFBundleExecutable_\x10\x16CFPlugInUnloadFunction]CFPlugInTypes_\x10\x15CFBundleDocumentTypes_\x10\x1fCFPlugInDynamicRegisterFunction_\x10\x19CFBundleDevelopmentRegion\\CFBundleName\xd1\x0e\x0f_\x10$9B6E3B91-772C-4DA9-9EFC-06B51846737E_\x10\x1dMetadataImporterPluginFactory_\x10\x19com.adiumX.adiumXimporterRNOS6.0S1.0_\x10\x16AdiumSpotlightImporterP\xd1\x17\x18_\x10$8B08C4BF-415B-11D8-B3F9-0003936726FC\xa1\x0e\xa1\x1a\xd3\x1b\x1c\x1d\x1e\x1f _\x10\x10CFBundleTypeRole_\x10\x0fLSTypeIsPackage_\x10\x12LSItemContentTypesZMDImporter\t\xa2!\"_\x10\x12com.adiumx.htmllog_\x10\x11com.adiumx.xmllogWEnglish_\x10\x16AdiumSpotlightImporter\x00\b\x00!\x005\x00J\x00h\x00\x88\x00\x9a\x00\xaf\x00\xc8\x00\xd6\x00\xee\x01\x10\x01\x019\x01<\x01c\x01\x83\x01\x9f\x01\xa2\x01\xa6\x01\xaa\x01\xc3\x01\xc7\x01\xee\x01\xf0\x02\x01\x00\x00\x00\x00\x00\x00\x00%\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x8c", "bplist00\xdc\x01\x02PlugInDynamicRegistration_\x10\x1dCFBundleInfoDictionaryVersion_\x10\x0fCFBundleVersion_\x10\x12CFBundleExecutable_\x10\x16CFPlugInUnloadFunction]CFPlugInTypes_\x10\x15CFBundleDocumentTypes_\x10\x1fCFPlugInDynamicRegisterFunction_\x10\x19CFBundleDevelopmentRegion\\CFBundleName\xd1\x0e\x0f_\x10$9B6E3B91-772C-4DA9-9EFC-06B51846737E_\x10\x1dMetadataImporterPluginFactory_\x10\x19com.adiumX.adiumXimporterRNOS6.0S1.0_\x10\x16AdiumSpotlightImporterP\xd1\x17\x18_\x10$8B08C4BF-415B-11D8-B3F9-0003936726FC\xa1\x0e\xa1\x1a\xd3\x1b\x1c\x1d\x1e\x1f _\x10\x10CFBundleTypeRole_\x10\x0fLSTypeIsPackage_\x10\x12LSItemContentTypesZMDImporter\t\xa2!\"_\x10\x12com.adiumx.htmllog_\x10\x11com.adiumx.xmllogWEnglish_\x10\x16AdiumSpotlightImporter\x00\b\x00!\x005\x00J\x00h\x00\x88\x00\x9a\x00\xaf\x00\xc8\x00\xd6\x00\xee\x01\x10\x01,\x019\x01<\x01c\x01\x83\x01\x9f\x01\xa2\x01\xa6\x01\xaa\x01\xc3\x01\xc4\x01\xc7\x01\xee\x01\xf0\x01\xf2\x01\xf9\x02\f\x02\x1e\x023\x02>\x02?\x02B\x02W\x02k\x02s\x02\x01\x00\x00\x00\x00\x00\x00\x00%\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x8c", "bplist00000000000000000000000000", "bplist00\xdc\r\x10\x11\x12\x13\x14\x15\x16\x19\x15#$_\x10\x11CFPlugInFactories_\x10\x12CFBundleIdentifier_\x10\x1bCFPlugInDynamicRegistration_\x10\x1dCFBundleInfoDictionaryVersion_\x10\x0fCFBundleVersion_\x10\x12CFBundleExecutable_\x10\x16CFPlugInUnloadFunction]CFPlugInTypes_\x10\x15CFBundleDocumentTypes_\x10\x1fCFPlugInDynamicRegisterFunction_\x10\x19CFBundleDevelopmentRegion\\CFBundleName\xd1\x0e\x0f_\x10$9B6E3B91-772C-4DA9-9EFC-06B51846737E_\x10\x1dMetadataImporterPluginFactory_\x10\x19com.adiumX.adiumXimporterRNOS6.0S1.0_\x10\x16AdiumSpotlightImporterP\xd1\x17\x18_\x10$8B08C4BF-415B-11D8-B3F9-0003936726FC\xa1\x0e\xa1\x1a\xd3\x1b\x1c\x1d\x1e\x1f _\x10\x10CFBundleTypeRole_\x10\x0fLSTypeIsPackage_\x10\x12LSItemContentTypesZMDImporter\t\xa2!\"_\x10\x12com.adiumx.htmllog_\x10\x11com.adiumx.xmllogWEnglish_\x10\x16AdiumSpotlightImporter\x00\b\x00J\x00h\x00\x88\x00\x9a\x00\xaf\x00\xc8\x00\xd6\x02\x01\x00\x00\x00\x00\x00\x00\x00%\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x8c", "bplist00\xdc\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x10\x11\x12\x13\x14\x15\x16\x19\x15#$_\x10\x11CFPlugInFactories_\x10\x12CFBundleIdentifier_\x10\x1bCFPlugInDynamicRegistration_\x10\x1dCFBundleInfoDictionaryVersion_\x10\x0fCFBundleVersion_\x10\x12CFBundleExecutable_\x10\x16CFPlugInUnloadFunction]CFPlugInTypes_\x10\x15CFBundleDocumentTypes_\x10\x1fCFPlugInDynamicRegisterFunction_\x10\x19CFBundleDevelopmentRegion\\CFBundleName\xd1\x0e\x0f_\x10$9B6E3B91-772C-4DA9-9EFC-06B51846737E_\x10\x1dMetadataImporterPluginFactory_\x10\x19com.adiumX.adiumXimporterRNOS6.0S1.0_\x10\x16AdiumSpotlightImporterP\xd1\x17\x18_\x10$8B08C4BF-415B-11D8-B3F9-0003936726FC\xa1\x0e\xa1\x1a\xd3\x1b\x1c\x1d\x1e\x1f _\x10\x10CFBundleTypeRole_\x10\x0fLSTypeIsPackage_\x10\x12LSItemContentTypesZMDImporter\t\xa2!\"_\x10\x12com.adiumx.htmllog_\x10\x11com.adiumx.xmllogWEnglish_\x10\x16AdiumSpotlightImporter\x00\b\x00!\x005\x00J\x00h\x00\x88\x00\x9a\x00\xaf\x00\xc8\x00\xd6\x00\xee\x01\x10\x01\x019\x01<\x01c\x01\x83\x01\x9f\x01\xa2\x01\xa6\x01\xaa\x01\xc3\x01\xc7\x01\xee\x01\x01\xf2\x01\xf9\x02\f\x02\x1e\x023\x02>\x02\x01\x00\x00\x00\x00\x00\x00\x00%\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x8c", "bplist00\xdc\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x10\x11\x12\x13\x14\x15\x16\x19\x15#$_\x10\x11CFPlugInFactories_\x10\x12CFBundleIdentifier_\x10\x1bCFPlugInDynamicRegistration_\x10\x1dCFBundleInfoDictionaryVersion_\x10\x0fCFBundleVersion_\x10\x12CFBundleExecutable_\x10\x16CFPlugInUnloadFunction]CFPlugInTypes_\x10\x15CFBundleDocumentTypes_\x10\x1fCFPlugInDynamicRegisterFunction_\x10\x19CFBundleDevelopmentRegion\\CFBundleName\xd1\x0e\x0f_\x10$9B6E3B91-772C-4DA9-9EFC-06B51846737E_\x10\x1dMetadataImporterPluginFactory_\x10\x19com.adiumX.adiumXimporterRNOS6.0S1.0_\x10\x16AdiumSpotlightImporterP\xd1\x17\x18_\x10$8B08C4BF-415B-11D8-B3F9-0003936726FC\xa1\x0e\xa1\x1a\xd3\x1b\x1c\x1d\x1e\x1f _\x10\x10CFBundleTypeRole_\x10\x0fLSTypeIsPackage_\x10\x12LSItemContentTypesZMDImporter\t\xa2!\"_\x10\x12com.adiumx.htmllog_\x10\x11com.adiumx.xmllogWEnglish_\x10\x16AdiumSpotlightImporter\x00\b5\x02\x01\x00\x00\x00\x00\x00\x00\x00%\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x8c", "bplist00\xd60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\b000\x01\x01\x00\x00\x00\x00\x00\x00\x00\x1d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6", "bplist00\xd6\x0100000\a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\xa2\x1b\x1c00000000000000000000000000000000\b\x8300\x01\x01\x00\x00\x00\x00\x00\x00\x00\x1d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6", "bplist00\xd6\x0100000\a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\b000\x01\x01\x00\x00\x00\x00\x00\x00\x00\x1d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6", "bplist00\xd5\x01\x02\x03\x04\x05\x06\a\b\t\n000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\x1300000000\b\xa6\x01\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaf", } func TestInvalidBinaryPlists(t *testing.T) { for idx, data := range InvalidBplists { var obj interface{} buf := strings.NewReader(data) fmt.Printf("%v %s\n", idx, obj) err := NewDecoder(buf).Decode(&obj) if err == nil { t.Fatal("invalid plist failed to throw error") } else { t.Log(err) } } } go-plist-master/README.md0000644000000000000000000000061012737755636014104 0ustar rootroot# plist - A pure Go property list transcoder ## 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"}) } ``` go-plist-master/util.go0000644000000000000000000000040712737755636014135 0ustar rootrootpackage 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 } go-plist-master/decode.go0000644000000000000000000000762012737755636014407 0ustar rootrootpackage plist import ( "bytes" "io" "reflect" "runtime" ) type parser interface { parseDocument() (*plistValue, 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 *plistValue 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 // []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 } go-plist-master/unmarshal.go0000644000000000000000000001511612737755636015155 0ustar rootrootpackage plist import ( "encoding" "fmt" "reflect" "time" ) type incompatibleDecodeTypeError struct { typ reflect.Type pKind plistKind } func (u *incompatibleDecodeTypeError) Error() string { return fmt.Sprintf("plist: type mismatch: tried to decode %v into value of type %v", plistKindNames[u.pKind], u.typ) } var ( textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() ) func isEmptyInterface(v reflect.Value) bool { return v.Kind() == reflect.Interface && v.NumMethod() == 0 } func (p *Decoder) unmarshalTextInterface(pval *plistValue, unmarshalable encoding.TextUnmarshaler) { err := unmarshalable.UnmarshalText([]byte(pval.value.(string))) if err != nil { panic(err) } } func (p *Decoder) unmarshalTime(pval *plistValue, val reflect.Value) { val.Set(reflect.ValueOf(pval.value.(time.Time))) } 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 *plistValue, 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.kind} // time.Time implements TextMarshaler, but we need to parse it as RFC3339 if pval.kind == Date { if val.Type() == timeType { p.unmarshalTime(pval, val) return } panic(incompatibleTypeError) } if val.CanInterface() && val.Type().Implements(textUnmarshalerType) && val.Type() != timeType { p.unmarshalTextInterface(pval, val.Interface().(encoding.TextUnmarshaler)) return } if val.CanAddr() { pv := val.Addr() if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) && val.Type() != timeType { p.unmarshalTextInterface(pval, pv.Interface().(encoding.TextUnmarshaler)) return } } typ := val.Type() switch pval.kind { case String: if val.Kind() == reflect.String { val.SetString(pval.value.(string)) return } if p.lax { p.unmarshalLaxString(pval.value.(string), val) return } panic(incompatibleTypeError) case Integer: switch val.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: val.SetInt(int64(pval.value.(signedInt).value)) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: val.SetUint(pval.value.(signedInt).value) default: panic(incompatibleTypeError) } case Real: if val.Kind() == reflect.Float32 || val.Kind() == reflect.Float64 { val.SetFloat(pval.value.(sizedFloat).value) } else { panic(incompatibleTypeError) } case Boolean: if val.Kind() == reflect.Bool { val.SetBool(pval.value.(bool)) } else { panic(incompatibleTypeError) } case Data: if val.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Uint8 { val.SetBytes(pval.value.([]byte)) } else { panic(incompatibleTypeError) } case Array: p.unmarshalArray(pval, val) case Dictionary: p.unmarshalDictionary(pval, val) } } func (p *Decoder) unmarshalArray(pval *plistValue, val reflect.Value) { subvalues := pval.value.([]*plistValue) var n int if val.Kind() == reflect.Slice { // Slice of element values. // Grow slice. cnt := len(subvalues) + 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(subvalues) > val.Cap() { panic(fmt.Errorf("plist: attempted to unmarshal %d values into an array of size %d", len(subvalues), val.Cap())) } } else { panic(&incompatibleDecodeTypeError{val.Type(), pval.kind}) } // Recur to read element into slice. for _, sval := range subvalues { p.unmarshal(sval, val.Index(n)) n++ } return } func (p *Decoder) unmarshalDictionary(pval *plistValue, val reflect.Value) { typ := val.Type() switch val.Kind() { case reflect.Struct: tinfo, err := getTypeInfo(typ) if err != nil { panic(err) } subvalues := pval.value.(*dictionary).m for _, finfo := range tinfo.fields { p.unmarshal(subvalues[finfo.name], finfo.value(val)) } case reflect.Map: if val.IsNil() { val.Set(reflect.MakeMap(typ)) } subvalues := pval.value.(*dictionary).m for k, sval := range subvalues { 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, pval.kind}) } } /* *Interface is modelled after encoding/json */ func (p *Decoder) valueInterface(pval *plistValue) interface{} { switch pval.kind { case String: return pval.value.(string) case Integer: if pval.value.(signedInt).signed { return int64(pval.value.(signedInt).value) } return pval.value.(signedInt).value case Real: bits := pval.value.(sizedFloat).bits switch bits { case 32: return float32(pval.value.(sizedFloat).value) case 64: return pval.value.(sizedFloat).value } case Boolean: return pval.value.(bool) case Array: return p.arrayInterface(pval.value.([]*plistValue)) case Dictionary: return p.dictionaryInterface(pval.value.(*dictionary)) case Data: return pval.value.([]byte) case Date: return pval.value.(time.Time) } return nil } func (p *Decoder) arrayInterface(subvalues []*plistValue) []interface{} { out := make([]interface{}, len(subvalues)) for i, subv := range subvalues { out[i] = p.valueInterface(subv) } return out } func (p *Decoder) dictionaryInterface(dict *dictionary) map[string]interface{} { out := make(map[string]interface{}) for k, subv := range dict.m { out[k] = p.valueInterface(subv) } return out } go-plist-master/text.go0000644000000000000000000002735512737755636014157 0ustar rootrootpackage plist import ( "bufio" "encoding/hex" "errors" "io" "runtime" "strconv" "strings" "time" ) type textPlistGenerator struct { writer io.Writer format int quotableTable *[4]uint64 indent string depth int dictKvDelimiter, dictEntryDelimiter, arrayDelimiter []byte } var ( textPlistTimeLayout = "2006-01-02 15:04:05 -0700" padding = "0000" ) func (p *textPlistGenerator) generateDocument(pval *plistValue) { 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)[c/64]&(1<<(c%64)) > 0 { 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 *plistValue) { if pval == nil { return } switch pval.kind { case Dictionary: p.writer.Write([]byte(`{`)) p.deltaIndent(1) dict := pval.value.(*dictionary) dict.populateArrays() for i, k := range dict.keys { p.writeIndent() io.WriteString(p.writer, p.plistQuotedString(k)) p.writer.Write(p.dictKvDelimiter) p.writePlistValue(dict.values[i]) p.writer.Write(p.dictEntryDelimiter) } p.deltaIndent(-1) p.writeIndent() p.writer.Write([]byte(`}`)) case Array: p.writer.Write([]byte(`(`)) p.deltaIndent(1) values := pval.value.([]*plistValue) for _, v := range values { p.writeIndent() p.writePlistValue(v) p.writer.Write(p.arrayDelimiter) } p.deltaIndent(-1) p.writeIndent() p.writer.Write([]byte(`)`)) case String: io.WriteString(p.writer, p.plistQuotedString(pval.value.(string))) case Integer: if p.format == GNUStepFormat { p.writer.Write([]byte(`<*I`)) } if pval.value.(signedInt).signed { io.WriteString(p.writer, strconv.FormatInt(int64(pval.value.(signedInt).value), 10)) } else { io.WriteString(p.writer, strconv.FormatUint(pval.value.(signedInt).value, 10)) } if p.format == GNUStepFormat { p.writer.Write([]byte(`>`)) } case Real: if p.format == GNUStepFormat { p.writer.Write([]byte(`<*R`)) } io.WriteString(p.writer, strconv.FormatFloat(pval.value.(sizedFloat).value, 'g', -1, 64)) if p.format == GNUStepFormat { p.writer.Write([]byte(`>`)) } case Boolean: b := pval.value.(bool) if p.format == GNUStepFormat { if b { p.writer.Write([]byte(`<*BY>`)) } else { p.writer.Write([]byte(`<*BN>`)) } } else { if b { p.writer.Write([]byte(`1`)) } else { p.writer.Write([]byte(`0`)) } } case Data: b := pval.value.([]byte) var hexencoded [9]byte var l int var asc = 9 hexencoded[8] = ' ' p.writer.Write([]byte(`<`)) 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 Date: if p.format == GNUStepFormat { p.writer.Write([]byte(`<*D`)) io.WriteString(p.writer, pval.value.(time.Time).In(time.UTC).Format(textPlistTimeLayout)) p.writer.Write([]byte(`>`)) } else { io.WriteString(p.writer, p.plistQuotedString(pval.value.(time.Time).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(`;`), } } type byteReader interface { io.Reader io.ByteScanner Peek(n int) ([]byte, error) ReadBytes(delim byte) ([]byte, error) } type textPlistParser struct { reader byteReader whitespaceReplacer *strings.Replacer format int } func (p *textPlistParser) parseDocument() (pval *plistValue, 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{"text", r.(error)} } } }() pval = p.parsePlistValue() return } func (p *textPlistParser) chugWhitespace() { ws: for { c, err := p.reader.ReadByte() if err != nil && err != io.EOF { panic(err) } if whitespace[c/64]&(1<<(c%64)) == 0 { if c == '/' && err != io.EOF { // A / at the end of the file is not the begining of a comment. cs, err := p.reader.Peek(1) if err != nil && err != io.EOF { panic(err) } c = cs[0] switch c { case '/': for { c, err = p.reader.ReadByte() if err != nil && err != io.EOF { panic(err) } else if err == io.EOF { break } // TODO: UTF-8 if c == '\n' || c == '\r' { break } } case '*': // Peek returned a value here, so it is safe to read. _, _ = p.reader.ReadByte() star := false for { c, err = p.reader.ReadByte() if err != nil { panic(err) } if c == '*' { star = true } else if c == '/' && star { break } else { star = false } } default: p.reader.UnreadByte() // Not the beginning of a // or /* comment break ws } continue } p.reader.UnreadByte() break } } } func (p *textPlistParser) parseQuotedString() *plistValue { escaping := false s := "" for { byt, err := p.reader.ReadByte() // EOF here is an error: we're inside a quoted string! if err != nil { panic(err) } c := rune(byt) if !escaping { if c == '"' { break } else if c == '\\' { escaping = true continue } } else { escaping = false // Everything that is not listed here passes through unharmed. switch c { case 'a': c = '\a' case 'b': c = '\b' case 'v': c = '\v' case 'f': c = '\f' case 't': c = '\t' case 'r': c = '\r' case 'n': c = '\n' case 'x', 'u', 'U': // hex and unicode l := 4 if c == 'x' { l = 2 } hex := make([]byte, l) p.reader.Read(hex) newc := mustParseInt(string(hex), 16, 16) c = rune(newc) case '0', '1', '2', '3', '4', '5', '6', '7': // octal! oct := make([]byte, 3) oct[0] = uint8(c) p.reader.Read(oct[1:]) newc := mustParseInt(string(oct), 8, 16) c = rune(newc) } } s += string(c) } return &plistValue{String, s} } func (p *textPlistParser) parseUnquotedString() *plistValue { s := "" for { c, err := p.reader.ReadByte() if err != nil { if err == io.EOF { break } panic(err) } // if we encounter a character that must be quoted, we're done. // the GNUStep quote table is more lax here, so we use it instead of the OpenStep one. if gsQuotable[c/64]&(1<<(c%64)) > 0 { p.reader.UnreadByte() break } s += string(c) } return &plistValue{String, s} } func (p *textPlistParser) parseDictionary() *plistValue { var keypv *plistValue subval := make(map[string]*plistValue) for { p.chugWhitespace() c, err := p.reader.ReadByte() // EOF here is an error: we're inside a dictionary! if err != nil { panic(err) } if c == '}' { break } else if c == '"' { keypv = p.parseQuotedString() } else { p.reader.UnreadByte() // Whoops, ate part of the string keypv = p.parseUnquotedString() } if keypv == nil { // TODO better error panic(errors.New("missing dictionary key")) } p.chugWhitespace() c, err = p.reader.ReadByte() if err != nil { panic(err) } if c != '=' { panic(errors.New("missing = in dictionary")) } // whitespace is guzzled within val := p.parsePlistValue() p.chugWhitespace() c, err = p.reader.ReadByte() if err != nil { panic(err) } if c != ';' { panic(errors.New("missing ; in dictionary")) } subval[keypv.value.(string)] = val } return &plistValue{Dictionary, &dictionary{m: subval}} } func (p *textPlistParser) parseArray() *plistValue { subval := make([]*plistValue, 0, 10) for { c, err := p.reader.ReadByte() // EOF here is an error: we're inside an array! if err != nil { panic(err) } if c == ')' { break } else if c == ',' { continue } p.reader.UnreadByte() pval := p.parsePlistValue() if pval.kind == String && pval.value.(string) == "" { continue } subval = append(subval, pval) } return &plistValue{Array, subval} } func (p *textPlistParser) parseGNUStepValue(v []byte) *plistValue { if len(v) < 3 { panic(errors.New("invalid GNUStep extended value")) } typ := v[1] v = v[2:] switch typ { case 'I': if v[0] == '-' { n := mustParseInt(string(v), 10, 64) return &plistValue{Integer, signedInt{uint64(n), true}} } else { n := mustParseUint(string(v), 10, 64) return &plistValue{Integer, signedInt{n, false}} } case 'R': n := mustParseFloat(string(v), 64) return &plistValue{Real, sizedFloat{n, 64}} case 'B': b := v[0] == 'Y' return &plistValue{Boolean, b} case 'D': t, err := time.Parse(textPlistTimeLayout, string(v)) if err != nil { panic(err) } return &plistValue{Date, t.In(time.UTC)} } panic(errors.New("invalid GNUStep type " + string(typ))) return nil } func (p *textPlistParser) parsePlistValue() *plistValue { for { p.chugWhitespace() c, err := p.reader.ReadByte() if err != nil && err != io.EOF { panic(err) } switch c { case '<': bytes, err := p.reader.ReadBytes('>') if err != nil { panic(err) } bytes = bytes[:len(bytes)-1] if len(bytes) == 0 { panic(errors.New("invalid empty angle-bracketed element")) } if bytes[0] == '*' { p.format = GNUStepFormat return p.parseGNUStepValue(bytes) } else { s := p.whitespaceReplacer.Replace(string(bytes)) data, err := hex.DecodeString(s) if err != nil { panic(err) } return &plistValue{Data, data} } case '"': return p.parseQuotedString() case '{': return p.parseDictionary() case '(': return p.parseArray() default: p.reader.UnreadByte() // Place back in buffer for parseUnquotedString return p.parseUnquotedString() } } return nil } func newTextPlistParser(r io.Reader) *textPlistParser { var reader byteReader if rd, ok := r.(byteReader); ok { reader = rd } else { reader = bufio.NewReader(r) } return &textPlistParser{ reader: reader, whitespaceReplacer: strings.NewReplacer("\t", "", "\n", "", " ", "", "\r", ""), format: OpenStepFormat, } }