|
|
// 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:] }
|