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