@@ -127,10 +127,9 @@ type IANAProperty struct {
127
127
}
128
128
129
129
var (
130
- propertyIanaTokenReg * regexp.Regexp
131
- propertyParamNameReg * regexp.Regexp
132
- propertyParamValueReg * regexp.Regexp
133
- propertyValueTextReg * regexp.Regexp
130
+ propertyIanaTokenReg * regexp.Regexp
131
+ propertyParamNameReg * regexp.Regexp
132
+ propertyValueTextReg * regexp.Regexp
134
133
)
135
134
136
135
func init () {
@@ -140,10 +139,6 @@ func init() {
140
139
log .Panicf ("Failed to build regex: %v" , err )
141
140
}
142
141
propertyParamNameReg = propertyIanaTokenReg
143
- propertyParamValueReg , err = regexp .Compile ("^(?:\" (?:[^\" \\ \\ ]|\\ [\" nrt])*\" |[^,;\\ \\ :\" ]*)" )
144
- if err != nil {
145
- log .Panicf ("Failed to build regex: %v" , err )
146
- }
147
142
propertyValueTextReg , err = regexp .Compile ("^.*" )
148
143
if err != nil {
149
144
log .Panicf ("Failed to build regex: %v" , err )
@@ -152,41 +147,46 @@ func init() {
152
147
153
148
type ContentLine string
154
149
155
- func ParseProperty (contentLine ContentLine ) * BaseProperty {
150
+ func ParseProperty (contentLine ContentLine ) ( * BaseProperty , error ) {
156
151
r := & BaseProperty {
157
152
ICalParameters : map [string ][]string {},
158
153
}
159
154
tokenPos := propertyIanaTokenReg .FindIndex ([]byte (contentLine ))
160
155
if tokenPos == nil {
161
- return nil
156
+ return nil , nil
162
157
}
163
158
p := 0
164
159
r .IANAToken = string (contentLine [p + tokenPos [0 ] : p + tokenPos [1 ]])
165
160
p += tokenPos [1 ]
166
161
for {
167
162
if p >= len (contentLine ) {
168
- return nil
163
+ return nil , nil
169
164
}
170
165
switch rune (contentLine [p ]) {
171
166
case ':' :
172
- return parsePropertyValue (r , string (contentLine ), p + 1 )
167
+ return parsePropertyValue (r , string (contentLine ), p + 1 ), nil
173
168
case ';' :
174
169
var np int
175
- r , np = parsePropertyParam (r , string (contentLine ), p + 1 )
170
+ var err error
171
+ t := r .IANAToken
172
+ r , np , err = parsePropertyParam (r , string (contentLine ), p + 1 )
173
+ if err != nil {
174
+ return nil , fmt .Errorf ("parsing property %s: %w" , t , err )
175
+ }
176
176
if r == nil {
177
- return nil
177
+ return nil , nil
178
178
}
179
179
p = np
180
180
default :
181
- return nil
181
+ return nil , nil
182
182
}
183
183
}
184
184
}
185
185
186
- func parsePropertyParam (r * BaseProperty , contentLine string , p int ) (* BaseProperty , int ) {
186
+ func parsePropertyParam (r * BaseProperty , contentLine string , p int ) (* BaseProperty , int , error ) {
187
187
tokenPos := propertyParamNameReg .FindIndex ([]byte (contentLine [p :]))
188
188
if tokenPos == nil {
189
- return nil , p
189
+ return nil , p , nil
190
190
}
191
191
k , v := "" , ""
192
192
k = string (contentLine [p : p + tokenPos [1 ]])
@@ -195,26 +195,89 @@ func parsePropertyParam(r *BaseProperty, contentLine string, p int) (*BaseProper
195
195
case '=' :
196
196
p += 1
197
197
default :
198
- return nil , p
198
+ return nil , p , fmt . Errorf ( "missing property value for %s in %s" , k , r . IANAToken )
199
199
}
200
200
for {
201
201
if p >= len (contentLine ) {
202
- return nil , p
202
+ return nil , p , nil
203
203
}
204
- tokenPos = propertyParamValueReg .FindIndex ([]byte (contentLine [p :]))
205
- if tokenPos == nil {
206
- return nil , p
204
+ var err error
205
+ v , p , err = parsePropertyParamValue (contentLine , p )
206
+ if err != nil {
207
+ return nil , 0 , fmt .Errorf ("parse error: %w %s in %s" , err , k , r .IANAToken )
207
208
}
208
- v = string (contentLine [p + tokenPos [0 ] : p + tokenPos [1 ]])
209
- p += tokenPos [1 ]
210
209
r .ICalParameters [k ] = append (r .ICalParameters [k ], v )
211
210
switch rune (contentLine [p ]) {
212
211
case ',' :
213
212
p += 1
214
213
default :
215
- return r , p
214
+ return r , p , nil
215
+ }
216
+ }
217
+ }
218
+
219
+ func parsePropertyParamValue (s string , p int ) (string , int , error ) {
220
+ /*
221
+ quoted-string = DQUOTE *QSAFE-CHAR DQUOTE
222
+
223
+ QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-US-ASCII
224
+ ; Any character except CONTROL and DQUOTE
225
+
226
+ SAFE-CHAR = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E
227
+ / NON-US-ASCII
228
+ ; Any character except CONTROL, DQUOTE, ";", ":", ","
229
+
230
+ text = *(TSAFE-CHAR / ":" / DQUOTE / ESCAPED-CHAR)
231
+ ; Folded according to description above
232
+
233
+ ESCAPED-CHAR = "\\" / "\;" / "\," / "\N" / "\n")
234
+ ; \\ encodes \, \N or \n encodes newline
235
+ ; \; encodes ;, \, encodes ,
236
+
237
+ TSAFE-CHAR = %x20-21 / %x23-2B / %x2D-39 / %x3C-5B
238
+ %x5D-7E / NON-US-ASCII
239
+ ; Any character except CTLs not needed by the current
240
+ ; character set, DQUOTE, ";", ":", "\", ","
241
+
242
+ CONTROL = %x00-08 / %x0A-1F / %x7F
243
+ ; All the controls except HTAB
244
+
245
+ */
246
+ r := make ([]byte , 0 , len (s ))
247
+ quoted := false
248
+ done := false
249
+ ip := p
250
+ for ; p < len (s ) && ! done ; p ++ {
251
+ switch s [p ] {
252
+ case 0x00 , 0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 :
253
+ return "" , 0 , fmt .Errorf ("unexpected char ascii:%d in property param value" , s [p ])
254
+ case 0x0A , 0x0B , 0x0C , 0x0D , 0x0E , 0x0F , 0x10 , 0x11 , 0x12 , 0x13 , 0x14 , 0x15 , 0x16 , 0x17 , 0x18 , 0x19 , 0x1A , 0x1B ,
255
+ 0x1C , 0x1D , 0x1E , 0x1F :
256
+ return "" , 0 , fmt .Errorf ("unexpected char ascii:%d in property param value" , s [p ])
257
+ case '\\' :
258
+ r = append (r , []byte (FromText (string (s [p + 1 :p + 2 ])))... )
259
+ p ++
260
+ continue
261
+ case ';' , ':' , ',' :
262
+ if ! quoted {
263
+ done = true
264
+ p --
265
+ continue
266
+ }
267
+ case '"' :
268
+ if p == ip {
269
+ quoted = true
270
+ continue
271
+ }
272
+ if quoted {
273
+ done = true
274
+ continue
275
+ }
276
+ return "" , 0 , fmt .Errorf ("unexpected double quote in property param value" )
216
277
}
278
+ r = append (r , s [p ])
217
279
}
280
+ return string (r ), p , nil
218
281
}
219
282
220
283
func parsePropertyValue (r * BaseProperty , contentLine string , p int ) * BaseProperty {
0 commit comments