Skip to content

Commit baf9fd4

Browse files
committed
go.net/webdav: new Handler, FileSystem, LockSystem and lockInfo types.
LGTM=dave R=nmvc, dave CC=bradfitz, dr.volker.dobler, golang-codereviews https://golang.org/cl/169240043
1 parent ef3d74d commit baf9fd4

File tree

7 files changed

+662
-78
lines changed

7 files changed

+662
-78
lines changed

webdav/file.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
5+
package webdav
6+
7+
import (
8+
"io"
9+
"net/http"
10+
"os"
11+
)
12+
13+
// TODO: comment that paths are always "/"-separated, even for Windows servers.
14+
15+
type FileSystem interface {
16+
Mkdir(path string, perm os.FileMode) error
17+
OpenFile(path string, flag int, perm os.FileMode) (File, error)
18+
RemoveAll(path string) error
19+
Stat(path string) (os.FileInfo, error)
20+
}
21+
22+
type File interface {
23+
http.File
24+
io.Writer
25+
}
26+
27+
// TODO: a MemFS implementation.
28+
// TODO: a RealFS implementation, backed by the real, OS-provided file system.

webdav/if.go

+7-15
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,10 @@ type ifHeader struct {
1616
lists []ifList
1717
}
1818

19-
// ifList is a conjunction (AND) of ifConditions, and an optional resource tag.
19+
// ifList is a conjunction (AND) of Conditions, and an optional resource tag.
2020
type ifList struct {
2121
resourceTag string
22-
conditions []ifCondition
23-
}
24-
25-
// ifCondition can match a WebDAV resource, based on a stateToken or ETag.
26-
// Exactly one of stateToken and entityTag should be non-empty.
27-
type ifCondition struct {
28-
not bool
29-
stateToken string
30-
entityTag string
22+
conditions []Condition
3123
}
3224

3325
// parseIfHeader parses the "If: foo bar" HTTP header. The httpHeader string
@@ -110,19 +102,19 @@ func parseList(s string) (l ifList, remaining string, ok bool) {
110102
}
111103
}
112104

113-
func parseCondition(s string) (c ifCondition, remaining string, ok bool) {
105+
func parseCondition(s string) (c Condition, remaining string, ok bool) {
114106
tokenType, tokenStr, s := lex(s)
115107
if tokenType == notTokenType {
116-
c.not = true
108+
c.Not = true
117109
tokenType, tokenStr, s = lex(s)
118110
}
119111
switch tokenType {
120112
case strTokenType, angleTokenType:
121-
c.stateToken = tokenStr
113+
c.Token = tokenStr
122114
case squareTokenType:
123-
c.entityTag = tokenStr
115+
c.ETag = tokenStr
124116
default:
125-
return ifCondition{}, "", false
117+
return Condition{}, "", false
126118
}
127119
return c, s, true
128120
}

webdav/if_test.go

+63-63
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func TestParseIfHeader(t *testing.T) {
3838
`<foo>`,
3939
ifHeader{},
4040
}, {
41-
"bad: no list after resource #1",
41+
"bad: no list after resource #2",
4242
`<foo> <bar> (a)`,
4343
ifHeader{},
4444
}, {
@@ -66,12 +66,12 @@ func TestParseIfHeader(t *testing.T) {
6666
`(Not Not a)`,
6767
ifHeader{},
6868
}, {
69-
"good: one list with a stateToken",
69+
"good: one list with a Token",
7070
`(a)`,
7171
ifHeader{
7272
lists: []ifList{{
73-
conditions: []ifCondition{{
74-
stateToken: `a`,
73+
conditions: []Condition{{
74+
Token: `a`,
7575
}},
7676
}},
7777
},
@@ -80,8 +80,8 @@ func TestParseIfHeader(t *testing.T) {
8080
`([a])`,
8181
ifHeader{
8282
lists: []ifList{{
83-
conditions: []ifCondition{{
84-
entityTag: `a`,
83+
conditions: []Condition{{
84+
ETag: `a`,
8585
}},
8686
}},
8787
},
@@ -90,15 +90,15 @@ func TestParseIfHeader(t *testing.T) {
9090
`(Not a Not b Not [d])`,
9191
ifHeader{
9292
lists: []ifList{{
93-
conditions: []ifCondition{{
94-
not: true,
95-
stateToken: `a`,
93+
conditions: []Condition{{
94+
Not: true,
95+
Token: `a`,
9696
}, {
97-
not: true,
98-
stateToken: `b`,
97+
Not: true,
98+
Token: `b`,
9999
}, {
100-
not: true,
101-
entityTag: `d`,
100+
Not: true,
101+
ETag: `d`,
102102
}},
103103
}},
104104
},
@@ -107,12 +107,12 @@ func TestParseIfHeader(t *testing.T) {
107107
`(a) (b)`,
108108
ifHeader{
109109
lists: []ifList{{
110-
conditions: []ifCondition{{
111-
stateToken: `a`,
110+
conditions: []Condition{{
111+
Token: `a`,
112112
}},
113113
}, {
114-
conditions: []ifCondition{{
115-
stateToken: `b`,
114+
conditions: []Condition{{
115+
Token: `b`,
116116
}},
117117
}},
118118
},
@@ -121,14 +121,14 @@ func TestParseIfHeader(t *testing.T) {
121121
`(Not a) (Not b)`,
122122
ifHeader{
123123
lists: []ifList{{
124-
conditions: []ifCondition{{
125-
not: true,
126-
stateToken: `a`,
124+
conditions: []Condition{{
125+
Not: true,
126+
Token: `a`,
127127
}},
128128
}, {
129-
conditions: []ifCondition{{
130-
not: true,
131-
stateToken: `b`,
129+
conditions: []Condition{{
130+
Not: true,
131+
Token: `b`,
132132
}},
133133
}},
134134
},
@@ -139,8 +139,8 @@ func TestParseIfHeader(t *testing.T) {
139139
ifHeader{
140140
lists: []ifList{{
141141
resourceTag: `http://www.example.com/users/f/fielding/index.html`,
142-
conditions: []ifCondition{{
143-
stateToken: `urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6`,
142+
conditions: []Condition{{
143+
Token: `urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6`,
144144
}},
145145
}},
146146
},
@@ -149,8 +149,8 @@ func TestParseIfHeader(t *testing.T) {
149149
`(<urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf>)`,
150150
ifHeader{
151151
lists: []ifList{{
152-
conditions: []ifCondition{{
153-
stateToken: `urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf`,
152+
conditions: []Condition{{
153+
Token: `urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf`,
154154
}},
155155
}},
156156
},
@@ -161,8 +161,8 @@ func TestParseIfHeader(t *testing.T) {
161161
ifHeader{
162162
lists: []ifList{{
163163
resourceTag: `http://example.com/locked/`,
164-
conditions: []ifCondition{{
165-
stateToken: `urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf`,
164+
conditions: []Condition{{
165+
Token: `urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf`,
166166
}},
167167
}},
168168
},
@@ -173,8 +173,8 @@ func TestParseIfHeader(t *testing.T) {
173173
ifHeader{
174174
lists: []ifList{{
175175
resourceTag: `http://example.com/locked/member`,
176-
conditions: []ifCondition{{
177-
stateToken: `urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf`,
176+
conditions: []Condition{{
177+
Token: `urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf`,
178178
}},
179179
}},
180180
},
@@ -184,12 +184,12 @@ func TestParseIfHeader(t *testing.T) {
184184
(<urn:uuid:e454f3f3-acdc-452a-56c7-00a5c91e4b77>)`,
185185
ifHeader{
186186
lists: []ifList{{
187-
conditions: []ifCondition{{
188-
stateToken: `urn:uuid:fe184f2e-6eec-41d0-c765-01adc56e6bb4`,
187+
conditions: []Condition{{
188+
Token: `urn:uuid:fe184f2e-6eec-41d0-c765-01adc56e6bb4`,
189189
}},
190190
}, {
191-
conditions: []ifCondition{{
192-
stateToken: `urn:uuid:e454f3f3-acdc-452a-56c7-00a5c91e4b77`,
191+
conditions: []Condition{{
192+
Token: `urn:uuid:e454f3f3-acdc-452a-56c7-00a5c91e4b77`,
193193
}},
194194
}},
195195
},
@@ -198,8 +198,8 @@ func TestParseIfHeader(t *testing.T) {
198198
`(<urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4>)`,
199199
ifHeader{
200200
lists: []ifList{{
201-
conditions: []ifCondition{{
202-
stateToken: `urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4`,
201+
conditions: []Condition{{
202+
Token: `urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4`,
203203
}},
204204
}},
205205
},
@@ -210,14 +210,14 @@ func TestParseIfHeader(t *testing.T) {
210210
(["I am another ETag"])`,
211211
ifHeader{
212212
lists: []ifList{{
213-
conditions: []ifCondition{{
214-
stateToken: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`,
213+
conditions: []Condition{{
214+
Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`,
215215
}, {
216-
entityTag: `"I am an ETag"`,
216+
ETag: `"I am an ETag"`,
217217
}},
218218
}, {
219-
conditions: []ifCondition{{
220-
entityTag: `"I am another ETag"`,
219+
conditions: []Condition{{
220+
ETag: `"I am another ETag"`,
221221
}},
222222
}},
223223
},
@@ -227,11 +227,11 @@ func TestParseIfHeader(t *testing.T) {
227227
<urn:uuid:58f202ac-22cf-11d1-b12d-002035b29092>)`,
228228
ifHeader{
229229
lists: []ifList{{
230-
conditions: []ifCondition{{
231-
not: true,
232-
stateToken: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`,
230+
conditions: []Condition{{
231+
Not: true,
232+
Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`,
233233
}, {
234-
stateToken: `urn:uuid:58f202ac-22cf-11d1-b12d-002035b29092`,
234+
Token: `urn:uuid:58f202ac-22cf-11d1-b12d-002035b29092`,
235235
}},
236236
}},
237237
},
@@ -241,13 +241,13 @@ func TestParseIfHeader(t *testing.T) {
241241
(Not <DAV:no-lock>)`,
242242
ifHeader{
243243
lists: []ifList{{
244-
conditions: []ifCondition{{
245-
stateToken: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`,
244+
conditions: []Condition{{
245+
Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`,
246246
}},
247247
}, {
248-
conditions: []ifCondition{{
249-
not: true,
250-
stateToken: `DAV:no-lock`,
248+
conditions: []Condition{{
249+
Not: true,
250+
Token: `DAV:no-lock`,
251251
}},
252252
}},
253253
},
@@ -259,15 +259,15 @@ func TestParseIfHeader(t *testing.T) {
259259
ifHeader{
260260
lists: []ifList{{
261261
resourceTag: `/resource1`,
262-
conditions: []ifCondition{{
263-
stateToken: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`,
262+
conditions: []Condition{{
263+
Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`,
264264
}, {
265-
entityTag: `W/"A weak ETag"`,
265+
ETag: `W/"A weak ETag"`,
266266
}},
267267
}, {
268268
resourceTag: `/resource1`,
269-
conditions: []ifCondition{{
270-
entityTag: `"strong ETag"`,
269+
conditions: []Condition{{
270+
ETag: `"strong ETag"`,
271271
}},
272272
}},
273273
},
@@ -278,8 +278,8 @@ func TestParseIfHeader(t *testing.T) {
278278
ifHeader{
279279
lists: []ifList{{
280280
resourceTag: `http://www.example.com/specs/`,
281-
conditions: []ifCondition{{
282-
stateToken: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`,
281+
conditions: []Condition{{
282+
Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`,
283283
}},
284284
}},
285285
},
@@ -289,8 +289,8 @@ func TestParseIfHeader(t *testing.T) {
289289
ifHeader{
290290
lists: []ifList{{
291291
resourceTag: `/specs/rfc2518.doc`,
292-
conditions: []ifCondition{{
293-
entityTag: `"4217"`,
292+
conditions: []Condition{{
293+
ETag: `"4217"`,
294294
}},
295295
}},
296296
},
@@ -300,9 +300,9 @@ func TestParseIfHeader(t *testing.T) {
300300
ifHeader{
301301
lists: []ifList{{
302302
resourceTag: `/specs/rfc2518.doc`,
303-
conditions: []ifCondition{{
304-
not: true,
305-
entityTag: `"4217"`,
303+
conditions: []Condition{{
304+
Not: true,
305+
ETag: `"4217"`,
306306
}},
307307
}},
308308
},

webdav/lock.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
5+
package webdav
6+
7+
import (
8+
"errors"
9+
"io"
10+
"time"
11+
)
12+
13+
var (
14+
ErrConfirmationFailed = errors.New("webdav: confirmation failed")
15+
ErrForbidden = errors.New("webdav: forbidden")
16+
ErrNoSuchLock = errors.New("webdav: no such lock")
17+
)
18+
19+
// Condition can match a WebDAV resource, based on a token or ETag.
20+
// Exactly one of Token and ETag should be non-empty.
21+
type Condition struct {
22+
Not bool
23+
Token string
24+
ETag string
25+
}
26+
27+
type LockSystem interface {
28+
// TODO: comment that the conditions should be ANDed together.
29+
Confirm(path string, conditions ...Condition) (c io.Closer, err error)
30+
// TODO: comment that token should be an absolute URI as defined by RFC 3986,
31+
// Section 4.3. In particular, it should not contain whitespace.
32+
Create(path string, now time.Time, ld LockDetails) (token string, c io.Closer, err error)
33+
Refresh(token string, now time.Time, duration time.Duration) (ld LockDetails, c io.Closer, err error)
34+
Unlock(token string) error
35+
}
36+
37+
type LockDetails struct {
38+
Depth int // Negative means infinite depth.
39+
Duration time.Duration // Negative means unlimited duration.
40+
OwnerXML string // Verbatim XML.
41+
Path string
42+
}
43+
44+
// TODO: a MemLS implementation.

0 commit comments

Comments
 (0)