1package plist
2
3import (
4	"encoding/base64"
5	"encoding/xml"
6	"errors"
7	"fmt"
8	"io"
9	"runtime"
10	"strings"
11	"time"
12)
13
14type xmlPlistParser struct {
15	reader             io.Reader
16	xmlDecoder         *xml.Decoder
17	whitespaceReplacer *strings.Replacer
18	ntags              int
19}
20
21func (p *xmlPlistParser) parseDocument() (pval cfValue, parseError error) {
22	defer func() {
23		if r := recover(); r != nil {
24			if _, ok := r.(runtime.Error); ok {
25				panic(r)
26			}
27			if _, ok := r.(invalidPlistError); ok {
28				parseError = r.(error)
29			} else {
30				// Wrap all non-invalid-plist errors.
31				parseError = plistParseError{"XML", r.(error)}
32			}
33		}
34	}()
35	for {
36		if token, err := p.xmlDecoder.Token(); err == nil {
37			if element, ok := token.(xml.StartElement); ok {
38				pval = p.parseXMLElement(element)
39				if p.ntags == 0 {
40					panic(invalidPlistError{"XML", errors.New("no elements encountered")})
41				}
42				return
43			}
44		} else {
45			// The first XML parse turned out to be invalid:
46			// we do not have an XML property list.
47			panic(invalidPlistError{"XML", err})
48		}
49	}
50}
51
52func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) cfValue {
53	var charData xml.CharData
54	switch element.Name.Local {
55	case "plist":
56		p.ntags++
57		for {
58			token, err := p.xmlDecoder.Token()
59			if err != nil {
60				panic(err)
61			}
62
63			if el, ok := token.(xml.EndElement); ok && el.Name.Local == "plist" {
64				break
65			}
66
67			if el, ok := token.(xml.StartElement); ok {
68				return p.parseXMLElement(el)
69			}
70		}
71		return nil
72	case "string":
73		p.ntags++
74		err := p.xmlDecoder.DecodeElement(&charData, &element)
75		if err != nil {
76			panic(err)
77		}
78
79		return cfString(charData)
80	case "integer":
81		p.ntags++
82		err := p.xmlDecoder.DecodeElement(&charData, &element)
83		if err != nil {
84			panic(err)
85		}
86
87		s := string(charData)
88		if len(s) == 0 {
89			panic(errors.New("invalid empty <integer/>"))
90		}
91
92		if s[0] == '-' {
93			s, base := unsignedGetBase(s[1:])
94			n := mustParseInt("-"+s, base, 64)
95			return &cfNumber{signed: true, value: uint64(n)}
96		} else {
97			s, base := unsignedGetBase(s)
98			n := mustParseUint(s, base, 64)
99			return &cfNumber{signed: false, value: n}
100		}
101	case "real":
102		p.ntags++
103		err := p.xmlDecoder.DecodeElement(&charData, &element)
104		if err != nil {
105			panic(err)
106		}
107
108		n := mustParseFloat(string(charData), 64)
109		return &cfReal{wide: true, value: n}
110	case "true", "false":
111		p.ntags++
112		p.xmlDecoder.Skip()
113
114		b := element.Name.Local == "true"
115		return cfBoolean(b)
116	case "date":
117		p.ntags++
118		err := p.xmlDecoder.DecodeElement(&charData, &element)
119		if err != nil {
120			panic(err)
121		}
122
123		t, err := time.ParseInLocation(time.RFC3339, string(charData), time.UTC)
124		if err != nil {
125			panic(err)
126		}
127
128		return cfDate(t)
129	case "data":
130		p.ntags++
131		err := p.xmlDecoder.DecodeElement(&charData, &element)
132		if err != nil {
133			panic(err)
134		}
135
136		str := p.whitespaceReplacer.Replace(string(charData))
137
138		l := base64.StdEncoding.DecodedLen(len(str))
139		bytes := make([]uint8, l)
140		l, err = base64.StdEncoding.Decode(bytes, []byte(str))
141		if err != nil {
142			panic(err)
143		}
144
145		return cfData(bytes[:l])
146	case "dict":
147		p.ntags++
148		var key *string
149		keys := make([]string, 0, 32)
150		values := make([]cfValue, 0, 32)
151		for {
152			token, err := p.xmlDecoder.Token()
153			if err != nil {
154				panic(err)
155			}
156
157			if el, ok := token.(xml.EndElement); ok && el.Name.Local == "dict" {
158				if key != nil {
159					panic(errors.New("missing value in dictionary"))
160				}
161				break
162			}
163
164			if el, ok := token.(xml.StartElement); ok {
165				if el.Name.Local == "key" {
166					var k string
167					p.xmlDecoder.DecodeElement(&k, &el)
168					key = &k
169				} else {
170					if key == nil {
171						panic(errors.New("missing key in dictionary"))
172					}
173					keys = append(keys, *key)
174					values = append(values, p.parseXMLElement(el))
175					key = nil
176				}
177			}
178		}
179
180		if len(keys) == 1 && keys[0] == "CF$UID" && len(values) == 1 {
181			if integer, ok := values[0].(*cfNumber); ok {
182				return cfUID(integer.value)
183			}
184		}
185
186		return &cfDictionary{keys: keys, values: values}
187	case "array":
188		p.ntags++
189		values := make([]cfValue, 0, 10)
190		for {
191			token, err := p.xmlDecoder.Token()
192			if err != nil {
193				panic(err)
194			}
195
196			if el, ok := token.(xml.EndElement); ok && el.Name.Local == "array" {
197				break
198			}
199
200			if el, ok := token.(xml.StartElement); ok {
201				values = append(values, p.parseXMLElement(el))
202			}
203		}
204		return &cfArray{values}
205	}
206	err := fmt.Errorf("encountered unknown element %s", element.Name.Local)
207	if p.ntags == 0 {
208		// If out first XML tag is invalid, it might be an openstep data element, ala <abab> or <0101>
209		panic(invalidPlistError{"XML", err})
210	}
211	panic(err)
212}
213
214func newXMLPlistParser(r io.Reader) *xmlPlistParser {
215	return &xmlPlistParser{r, xml.NewDecoder(r), strings.NewReplacer("\t", "", "\n", "", " ", "", "\r", ""), 0}
216}
217