1// Copyright 2014 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package webdav
6
7// The If header is covered by Section 10.4.
8// http://www.webdav.org/specs/rfc4918.html#HEADER_If
9
10import (
11	"strings"
12)
13
14// ifHeader is a disjunction (OR) of ifLists.
15type ifHeader struct {
16	lists []ifList
17}
18
19// ifList is a conjunction (AND) of Conditions, and an optional resource tag.
20type ifList struct {
21	resourceTag string
22	conditions  []Condition
23}
24
25// parseIfHeader parses the "If: foo bar" HTTP header. The httpHeader string
26// should omit the "If:" prefix and have any "\r\n"s collapsed to a " ", as is
27// returned by req.Header.Get("If") for a http.Request req.
28func parseIfHeader(httpHeader string) (h ifHeader, ok bool) {
29	s := strings.TrimSpace(httpHeader)
30	switch tokenType, _, _ := lex(s); tokenType {
31	case '(':
32		return parseNoTagLists(s)
33	case angleTokenType:
34		return parseTaggedLists(s)
35	default:
36		return ifHeader{}, false
37	}
38}
39
40func parseNoTagLists(s string) (h ifHeader, ok bool) {
41	for {
42		l, remaining, ok := parseList(s)
43		if !ok {
44			return ifHeader{}, false
45		}
46		h.lists = append(h.lists, l)
47		if remaining == "" {
48			return h, true
49		}
50		s = remaining
51	}
52}
53
54func parseTaggedLists(s string) (h ifHeader, ok bool) {
55	resourceTag, n := "", 0
56	for first := true; ; first = false {
57		tokenType, tokenStr, remaining := lex(s)
58		switch tokenType {
59		case angleTokenType:
60			if !first && n == 0 {
61				return ifHeader{}, false
62			}
63			resourceTag, n = tokenStr, 0
64			s = remaining
65		case '(':
66			n++
67			l, remaining, ok := parseList(s)
68			if !ok {
69				return ifHeader{}, false
70			}
71			l.resourceTag = resourceTag
72			h.lists = append(h.lists, l)
73			if remaining == "" {
74				return h, true
75			}
76			s = remaining
77		default:
78			return ifHeader{}, false
79		}
80	}
81}
82
83func parseList(s string) (l ifList, remaining string, ok bool) {
84	tokenType, _, s := lex(s)
85	if tokenType != '(' {
86		return ifList{}, "", false
87	}
88	for {
89		tokenType, _, remaining = lex(s)
90		if tokenType == ')' {
91			if len(l.conditions) == 0 {
92				return ifList{}, "", false
93			}
94			return l, remaining, true
95		}
96		c, remaining, ok := parseCondition(s)
97		if !ok {
98			return ifList{}, "", false
99		}
100		l.conditions = append(l.conditions, c)
101		s = remaining
102	}
103}
104
105func parseCondition(s string) (c Condition, remaining string, ok bool) {
106	tokenType, tokenStr, s := lex(s)
107	if tokenType == notTokenType {
108		c.Not = true
109		tokenType, tokenStr, s = lex(s)
110	}
111	switch tokenType {
112	case strTokenType, angleTokenType:
113		c.Token = tokenStr
114	case squareTokenType:
115		c.ETag = tokenStr
116	default:
117		return Condition{}, "", false
118	}
119	return c, s, true
120}
121
122// Single-rune tokens like '(' or ')' have a token type equal to their rune.
123// All other tokens have a negative token type.
124const (
125	errTokenType    = rune(-1)
126	eofTokenType    = rune(-2)
127	strTokenType    = rune(-3)
128	notTokenType    = rune(-4)
129	angleTokenType  = rune(-5)
130	squareTokenType = rune(-6)
131)
132
133func lex(s string) (tokenType rune, tokenStr string, remaining string) {
134	// The net/textproto Reader that parses the HTTP header will collapse
135	// Linear White Space that spans multiple "\r\n" lines to a single " ",
136	// so we don't need to look for '\r' or '\n'.
137	for len(s) > 0 && (s[0] == '\t' || s[0] == ' ') {
138		s = s[1:]
139	}
140	if len(s) == 0 {
141		return eofTokenType, "", ""
142	}
143	i := 0
144loop:
145	for ; i < len(s); i++ {
146		switch s[i] {
147		case '\t', ' ', '(', ')', '<', '>', '[', ']':
148			break loop
149		}
150	}
151
152	if i != 0 {
153		tokenStr, remaining = s[:i], s[i:]
154		if tokenStr == "Not" {
155			return notTokenType, "", remaining
156		}
157		return strTokenType, tokenStr, remaining
158	}
159
160	j := 0
161	switch s[0] {
162	case '<':
163		j, tokenType = strings.IndexByte(s, '>'), angleTokenType
164	case '[':
165		j, tokenType = strings.IndexByte(s, ']'), squareTokenType
166	default:
167		return rune(s[0]), "", s[1:]
168	}
169	if j < 0 {
170		return errTokenType, "", ""
171	}
172	return tokenType, s[1:j], s[j+1:]
173}
174