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 dict := &cfDictionary{keys: keys, values: values} 181 return dict.maybeUID(false) 182 case "array": 183 p.ntags++ 184 values := make([]cfValue, 0, 10) 185 for { 186 token, err := p.xmlDecoder.Token() 187 if err != nil { 188 panic(err) 189 } 190 191 if el, ok := token.(xml.EndElement); ok && el.Name.Local == "array" { 192 break 193 } 194 195 if el, ok := token.(xml.StartElement); ok { 196 values = append(values, p.parseXMLElement(el)) 197 } 198 } 199 return &cfArray{values} 200 } 201 err := fmt.Errorf("encountered unknown element %s", element.Name.Local) 202 if p.ntags == 0 { 203 // If out first XML tag is invalid, it might be an openstep data element, ala <abab> or <0101> 204 panic(invalidPlistError{"XML", err}) 205 } 206 panic(err) 207} 208 209func newXMLPlistParser(r io.Reader) *xmlPlistParser { 210 return &xmlPlistParser{r, xml.NewDecoder(r), strings.NewReplacer("\t", "", "\n", "", " ", "", "\r", ""), 0} 211} 212