174 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			174 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2014 The Go Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package webdav
 | |
| 
 | |
| // The If header is covered by Section 10.4.
 | |
| // http://www.webdav.org/specs/rfc4918.html#HEADER_If
 | |
| 
 | |
| import (
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // ifHeader is a disjunction (OR) of ifLists.
 | |
| type ifHeader struct {
 | |
| 	lists []ifList
 | |
| }
 | |
| 
 | |
| // ifList is a conjunction (AND) of Conditions, and an optional resource tag.
 | |
| type ifList struct {
 | |
| 	resourceTag string
 | |
| 	conditions  []Condition
 | |
| }
 | |
| 
 | |
| // parseIfHeader parses the "If: foo bar" HTTP header. The httpHeader string
 | |
| // should omit the "If:" prefix and have any "\r\n"s collapsed to a " ", as is
 | |
| // returned by req.Header.Get("If") for a http.Request req.
 | |
| func parseIfHeader(httpHeader string) (h ifHeader, ok bool) {
 | |
| 	s := strings.TrimSpace(httpHeader)
 | |
| 	switch tokenType, _, _ := lex(s); tokenType {
 | |
| 	case '(':
 | |
| 		return parseNoTagLists(s)
 | |
| 	case angleTokenType:
 | |
| 		return parseTaggedLists(s)
 | |
| 	default:
 | |
| 		return ifHeader{}, false
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func parseNoTagLists(s string) (h ifHeader, ok bool) {
 | |
| 	for {
 | |
| 		l, remaining, ok := parseList(s)
 | |
| 		if !ok {
 | |
| 			return ifHeader{}, false
 | |
| 		}
 | |
| 		h.lists = append(h.lists, l)
 | |
| 		if remaining == "" {
 | |
| 			return h, true
 | |
| 		}
 | |
| 		s = remaining
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func parseTaggedLists(s string) (h ifHeader, ok bool) {
 | |
| 	resourceTag, n := "", 0
 | |
| 	for first := true; ; first = false {
 | |
| 		tokenType, tokenStr, remaining := lex(s)
 | |
| 		switch tokenType {
 | |
| 		case angleTokenType:
 | |
| 			if !first && n == 0 {
 | |
| 				return ifHeader{}, false
 | |
| 			}
 | |
| 			resourceTag, n = tokenStr, 0
 | |
| 			s = remaining
 | |
| 		case '(':
 | |
| 			n++
 | |
| 			l, remaining, ok := parseList(s)
 | |
| 			if !ok {
 | |
| 				return ifHeader{}, false
 | |
| 			}
 | |
| 			l.resourceTag = resourceTag
 | |
| 			h.lists = append(h.lists, l)
 | |
| 			if remaining == "" {
 | |
| 				return h, true
 | |
| 			}
 | |
| 			s = remaining
 | |
| 		default:
 | |
| 			return ifHeader{}, false
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func parseList(s string) (l ifList, remaining string, ok bool) {
 | |
| 	tokenType, _, s := lex(s)
 | |
| 	if tokenType != '(' {
 | |
| 		return ifList{}, "", false
 | |
| 	}
 | |
| 	for {
 | |
| 		tokenType, _, remaining = lex(s)
 | |
| 		if tokenType == ')' {
 | |
| 			if len(l.conditions) == 0 {
 | |
| 				return ifList{}, "", false
 | |
| 			}
 | |
| 			return l, remaining, true
 | |
| 		}
 | |
| 		c, remaining, ok := parseCondition(s)
 | |
| 		if !ok {
 | |
| 			return ifList{}, "", false
 | |
| 		}
 | |
| 		l.conditions = append(l.conditions, c)
 | |
| 		s = remaining
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func parseCondition(s string) (c Condition, remaining string, ok bool) {
 | |
| 	tokenType, tokenStr, s := lex(s)
 | |
| 	if tokenType == notTokenType {
 | |
| 		c.Not = true
 | |
| 		tokenType, tokenStr, s = lex(s)
 | |
| 	}
 | |
| 	switch tokenType {
 | |
| 	case strTokenType, angleTokenType:
 | |
| 		c.Token = tokenStr
 | |
| 	case squareTokenType:
 | |
| 		c.ETag = tokenStr
 | |
| 	default:
 | |
| 		return Condition{}, "", false
 | |
| 	}
 | |
| 	return c, s, true
 | |
| }
 | |
| 
 | |
| // Single-rune tokens like '(' or ')' have a token type equal to their rune.
 | |
| // All other tokens have a negative token type.
 | |
| const (
 | |
| 	errTokenType    = rune(-1)
 | |
| 	eofTokenType    = rune(-2)
 | |
| 	strTokenType    = rune(-3)
 | |
| 	notTokenType    = rune(-4)
 | |
| 	angleTokenType  = rune(-5)
 | |
| 	squareTokenType = rune(-6)
 | |
| )
 | |
| 
 | |
| func lex(s string) (tokenType rune, tokenStr string, remaining string) {
 | |
| 	// The net/textproto Reader that parses the HTTP header will collapse
 | |
| 	// Linear White Space that spans multiple "\r\n" lines to a single " ",
 | |
| 	// so we don't need to look for '\r' or '\n'.
 | |
| 	for len(s) > 0 && (s[0] == '\t' || s[0] == ' ') {
 | |
| 		s = s[1:]
 | |
| 	}
 | |
| 	if len(s) == 0 {
 | |
| 		return eofTokenType, "", ""
 | |
| 	}
 | |
| 	i := 0
 | |
| loop:
 | |
| 	for ; i < len(s); i++ {
 | |
| 		switch s[i] {
 | |
| 		case '\t', ' ', '(', ')', '<', '>', '[', ']':
 | |
| 			break loop
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if i != 0 {
 | |
| 		tokenStr, remaining = s[:i], s[i:]
 | |
| 		if tokenStr == "Not" {
 | |
| 			return notTokenType, "", remaining
 | |
| 		}
 | |
| 		return strTokenType, tokenStr, remaining
 | |
| 	}
 | |
| 
 | |
| 	j := 0
 | |
| 	switch s[0] {
 | |
| 	case '<':
 | |
| 		j, tokenType = strings.IndexByte(s, '>'), angleTokenType
 | |
| 	case '[':
 | |
| 		j, tokenType = strings.IndexByte(s, ']'), squareTokenType
 | |
| 	default:
 | |
| 		return rune(s[0]), "", s[1:]
 | |
| 	}
 | |
| 	if j < 0 {
 | |
| 		return errTokenType, "", ""
 | |
| 	}
 | |
| 	return tokenType, s[1:j], s[j+1:]
 | |
| }
 |