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,94 @@ 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
}
285
+ } else if len (fields ) == 0 {
286
+ return "" , errors .New ("msgauth: no identifier found" )
251
287
}
252
- return
288
+ return strings . TrimSpace ( fields [ 0 ]), nil
253
289
}
254
290
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" {
291
+ // getResults parses the authentication part of the authres header and returns
292
+ // a Result struct. Ignore header comments in parenthesis.
293
+ func ( p * parser ) getResult () ( result Result , err error ) {
294
+ method , resultvalue , err := p . keyValue ( )
295
+ if method == "none" {
260
296
return nil , nil
261
297
}
262
-
263
- k , v , err := parseParam (parts [0 ])
264
298
if err != nil {
265
299
return nil , err
266
300
}
267
- method , value := k , ResultValue (strings .ToLower (v ))
301
+ value := ResultValue (strings .ToLower (resultvalue ))
268
302
269
303
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
304
+ var k , v string
305
+ for {
306
+ k , v , err = p .keyValue ()
307
+ if k != "" {
308
+ params [k ] = v
309
+ }
310
+ if err == io .EOF {
311
+ break
312
+ } else if err != nil {
313
+ return nil , err
274
314
}
275
-
276
- params [k ] = v
277
315
}
278
316
279
317
newResult , ok := results [method ]
@@ -293,10 +331,114 @@ func parseResult(s string) (Result, error) {
293
331
return r , nil
294
332
}
295
333
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" )
334
+ // keyValue parses a sequence of key=value parameters
335
+ func (p * parser ) keyValue () (k , v string , err error ) {
336
+ k , err = p .readKey ()
337
+ if err != nil {
338
+ return
339
+ }
340
+ v , err = p .readValue ()
341
+ if err != nil {
342
+ return
300
343
}
301
- return strings .ToLower (strings .TrimSpace (kv [0 ])), strings .TrimSpace (kv [1 ]), nil
344
+ return
345
+ }
346
+
347
+ // readKey reads a method, reason or ptype.property as defined in RFC 8601
348
+ // Section 2.2. Ignore the method-version of the methodspec. Stop at EOF or the
349
+ // equal sign.
350
+ func (p * parser ) readKey () (k string , err error ) {
351
+ var c byte
352
+ for err != io .EOF {
353
+ c , err = p .r .ReadByte ()
354
+ if err != nil {
355
+ break
356
+ }
357
+ switch c {
358
+ case ';' :
359
+ err = io .EOF
360
+ break
361
+ case '=' :
362
+ break
363
+ case '(' :
364
+ p .r .UnreadByte ()
365
+ _ , err = p .readComment ()
366
+ continue
367
+ case '/' :
368
+ p .r .ReadBytes ('=' )
369
+ p .r .UnreadByte ()
370
+ default :
371
+ if ! unicode .IsSpace (rune (c )) {
372
+ k += string (c )
373
+ }
374
+ }
375
+ if c == '=' {
376
+ break
377
+ }
378
+ }
379
+ k = strings .TrimSpace (strings .ToLower (k ))
380
+ return
381
+ }
382
+
383
+ // readValue reads a result or value as defined in RFC 8601 Section 2.2. Value
384
+ // is defined as either a token or quoted string according to RFC 2045 Section
385
+ // 5.1. Stop at EOF, white space or semi-colons.
386
+ func (p * parser ) readValue () (v string , err error ) {
387
+ var c byte
388
+ for err != io .EOF {
389
+ c , err = p .r .ReadByte ()
390
+ if err != nil {
391
+ break
392
+ }
393
+ switch c {
394
+ case ';' :
395
+ err = io .EOF
396
+ break
397
+ case '(' :
398
+ p .r .UnreadByte ()
399
+ _ , err = p .readComment ()
400
+ continue
401
+ case '"' :
402
+ v , err = p .r .ReadString (c )
403
+ v = strings .TrimSuffix (v , string (c ))
404
+ default :
405
+ if ! unicode .IsSpace (rune (c )) {
406
+ v += string (c )
407
+ }
408
+ }
409
+ if unicode .IsSpace (rune (c )) {
410
+ if v != "" {
411
+ break
412
+ }
413
+ }
414
+ }
415
+ v = strings .TrimSpace (v )
416
+ return
417
+ }
418
+
419
+ func (p * parser ) readComment () (comment string , err error ) {
420
+ count := 0
421
+ var c byte
422
+ for {
423
+ c , err = p .r .ReadByte ()
424
+ if err != nil {
425
+ break
426
+ }
427
+ switch c {
428
+ case '\\' :
429
+ c , _ = p .r .ReadByte ()
430
+ comment += "\\ " + string (c )
431
+ case '(' :
432
+ count ++
433
+ case ')' :
434
+ count --
435
+ default :
436
+ comment += string (c )
437
+ }
438
+ if count == 0 {
439
+ break
440
+ }
441
+ }
442
+ comment = strings .TrimSpace (comment )
443
+ return
302
444
}
0 commit comments