1
1
package authres
2
2
3
3
import (
4
+ "bufio"
4
5
"errors"
6
+ "io"
5
7
"strings"
6
8
"unicode"
7
9
)
@@ -222,58 +224,92 @@ var results = map[string]newResultFunc{
222
224
// Parse parses the provided Authentication-Results header field. It returns the
223
225
// authentication service identifier and authentication results.
224
226
func Parse (v string ) (identifier string , results []Result , err error ) {
225
- parts := strings . Split ( v , ";" )
227
+ p := newParser ( v )
226
228
227
- identifier = strings .TrimSpace (parts [0 ])
228
- i := strings .IndexFunc (identifier , unicode .IsSpace )
229
- if i > 0 {
230
- version := strings .TrimSpace (identifier [i :])
231
- if version != "1" {
232
- return "" , nil , errors .New ("msgauth: unsupported version" )
233
- }
229
+ identifier , err = p .getIndentifier ()
230
+ if err != nil {
231
+ return identifier , nil , err
232
+ }
234
233
235
- identifier = identifier [:i ]
234
+ for {
235
+ result , err := p .getResult ()
236
+ if result == nil {
237
+ break
238
+ }
239
+ results = append (results , result )
240
+ if err == io .EOF {
241
+ break
242
+ } else if err != nil {
243
+ return identifier , results , err
244
+ }
236
245
}
237
246
238
- for i := 1 ; i < len (parts ); i ++ {
239
- s := strings .TrimSpace (parts [i ])
240
- if s == "" {
247
+ return identifier , results , nil
248
+ }
249
+
250
+ type parser struct {
251
+ r * bufio.Reader
252
+ }
253
+
254
+ func newParser (v string ) * parser {
255
+ return & parser {r : bufio .NewReader (strings .NewReader (v ))}
256
+ }
257
+
258
+ // getIdentifier parses the authserv-id of the authres header and checks the
259
+ // version id when present. Ignore header comments in parenthesis.
260
+ func (p * parser ) getIndentifier () (identifier string , err error ) {
261
+ for {
262
+ c , err := p .r .ReadByte ()
263
+ if err == io .EOF {
264
+ return identifier , nil
265
+ } else if err != nil {
266
+ return identifier , err
267
+ }
268
+ if c == '(' {
269
+ p .r .UnreadByte ()
270
+ p .readComment ()
241
271
continue
242
272
}
243
-
244
- result , err := parseResult (s )
245
- if err != nil {
246
- return identifier , results , err
273
+ if c == ';' {
274
+ break
247
275
}
248
- if result != nil {
249
- results = append (results , result )
276
+ identifier += string (c )
277
+ }
278
+
279
+ fields := strings .Fields (identifier )
280
+ if len (fields ) > 1 {
281
+ version := strings .TrimSpace (fields [1 ])
282
+ if version != "1" {
283
+ return "" , errors .New ("msgauth: unknown version" )
250
284
}
251
285
}
252
- return
286
+ return strings . TrimSpace ( fields [ 0 ]), nil
253
287
}
254
288
255
- func parseResult ( s string ) ( Result , error ) {
256
- // TODO: ignore header comments in parenthesis
257
-
258
- parts := strings . Fields ( s )
259
- if len ( parts ) == 0 || parts [ 0 ] == "none" {
289
+ // getResults parses the authentication part of the authres header and returns
290
+ // a Result struct. Ignore header comments in parenthesis.
291
+ func ( p * parser ) getResult () ( result Result , err error ) {
292
+ method , resultvalue , err := p . keyValue ( )
293
+ if method == "none" {
260
294
return nil , nil
261
295
}
262
-
263
- k , v , err := parseParam (parts [0 ])
264
296
if err != nil {
265
297
return nil , err
266
298
}
267
- method , value := k , ResultValue (strings .ToLower (v ))
299
+ value := ResultValue (strings .ToLower (resultvalue ))
268
300
269
301
params := make (map [string ]string )
270
- for i := 1 ; i < len (parts ); i ++ {
271
- k , v , err := parseParam (parts [i ])
272
- if err != nil {
273
- continue
302
+ var k , v string
303
+ for {
304
+ k , v , err = p .keyValue ()
305
+ if k != "" {
306
+ params [k ] = v
307
+ }
308
+ if err == io .EOF {
309
+ break
310
+ } else if err != nil {
311
+ return nil , err
274
312
}
275
-
276
- params [k ] = v
277
313
}
278
314
279
315
newResult , ok := results [method ]
@@ -293,10 +329,114 @@ func parseResult(s string) (Result, error) {
293
329
return r , nil
294
330
}
295
331
296
- func parseParam (s string ) (k string , v string , err error ) {
297
- kv := strings .SplitN (s , "=" , 2 )
298
- if len (kv ) != 2 {
299
- return "" , "" , errors .New ("msgauth: malformed authentication method and value" )
332
+ // keyValue parses a sequence of key=value parameters
333
+ func (p * parser ) keyValue () (k , v string , err error ) {
334
+ k , err = p .readKey ()
335
+ if err != nil {
336
+ return
337
+ }
338
+ v , err = p .readValue ()
339
+ if err != nil {
340
+ return
300
341
}
301
- return strings .ToLower (strings .TrimSpace (kv [0 ])), strings .TrimSpace (kv [1 ]), nil
342
+ return
343
+ }
344
+
345
+ // readKey reads a method, reason or ptype.property as defined in RFC 8601
346
+ // Section 2.2. Ignore the method-version of the methodspec. Stop at EOF or the
347
+ // equal sign.
348
+ func (p * parser ) readKey () (k string , err error ) {
349
+ var c byte
350
+ for err != io .EOF {
351
+ c , err = p .r .ReadByte ()
352
+ if err != nil {
353
+ break
354
+ }
355
+ switch c {
356
+ case ';' :
357
+ err = io .EOF
358
+ break
359
+ case '=' :
360
+ break
361
+ case '(' :
362
+ p .r .UnreadByte ()
363
+ _ , err = p .readComment ()
364
+ continue
365
+ case '/' :
366
+ p .r .ReadBytes ('=' )
367
+ p .r .UnreadByte ()
368
+ default :
369
+ if ! unicode .IsSpace (rune (c )) {
370
+ k += string (c )
371
+ }
372
+ }
373
+ if c == '=' {
374
+ break
375
+ }
376
+ }
377
+ k = strings .TrimSpace (strings .ToLower (k ))
378
+ return
379
+ }
380
+
381
+ // readValue reads a result or value as defined in RFC 8601 Section 2.2. Value
382
+ // is defined as either a token or quoted string according to RFC 2045 Section
383
+ // 5.1. Stop at EOF, white space or semi-colons.
384
+ func (p * parser ) readValue () (v string , err error ) {
385
+ var c byte
386
+ for err != io .EOF {
387
+ c , err = p .r .ReadByte ()
388
+ if err != nil {
389
+ break
390
+ }
391
+ switch c {
392
+ case ';' :
393
+ err = io .EOF
394
+ break
395
+ case '(' :
396
+ p .r .UnreadByte ()
397
+ _ , err = p .readComment ()
398
+ continue
399
+ case '"' :
400
+ v , err = p .r .ReadString (c )
401
+ v = strings .TrimSuffix (v , string (c ))
402
+ default :
403
+ if ! unicode .IsSpace (rune (c )) {
404
+ v += string (c )
405
+ }
406
+ }
407
+ if unicode .IsSpace (rune (c )) {
408
+ if v != "" {
409
+ break
410
+ }
411
+ }
412
+ }
413
+ v = strings .TrimSpace (v )
414
+ return
415
+ }
416
+
417
+ func (p * parser ) readComment () (comment string , err error ) {
418
+ count := 0
419
+ var c byte
420
+ for {
421
+ c , err = p .r .ReadByte ()
422
+ if err != nil {
423
+ break
424
+ }
425
+ switch c {
426
+ case '\\' :
427
+ c , _ = p .r .ReadByte ()
428
+ comment += "\\ " + string (c )
429
+ case '(' :
430
+ count ++
431
+ case ')' :
432
+ count --
433
+ default :
434
+ comment += string (c )
435
+ }
436
+ if count == 0 {
437
+ break
438
+ }
439
+ }
440
+ comment = strings .TrimSpace (comment )
441
+ return
302
442
}
0 commit comments