Refine some capital letters. Finish the Lock method.

This commit is contained in:
lishuang
2020-05-05 22:29:16 +08:00
parent a314d4dc54
commit ed0aa017db
10 changed files with 374 additions and 260 deletions

View File

@ -14,6 +14,7 @@ import (
"path" "path"
"regexp" "regexp"
"strings" "strings"
"time"
) )
/** /**
@ -28,6 +29,7 @@ type DavService struct {
BaseBean BaseBean
matterDao *MatterDao matterDao *MatterDao
matterService *MatterService matterService *MatterService
lockSystem webdav.LockSystem
} }
func (this *DavService) Init() { func (this *DavService) Init() {
@ -43,6 +45,8 @@ func (this *DavService) Init() {
this.matterService = b this.matterService = b
} }
// init the webdav lock system.
this.lockSystem = webdav.NewMemLS()
} }
//get the depth in header. Not support infinity yet. //get the depth in header. Not support infinity yet.
@ -536,9 +540,100 @@ func (this *DavService) HandleCopy(writer http.ResponseWriter, request *http.Req
} }
//lock. //lock.
func (this *DavService) HandleLock(writer http.ResponseWriter, request *http.Request, user *User, subPath string) { func (this *DavService) HandleLock(w http.ResponseWriter, r *http.Request, user *User, subPath string) {
xLimits := r.Header.Get("X-Litmus")
if xLimits == "locks: 6 (lock_excl)" {
fmt.Println("stop here!")
}
duration, err := webdav.ParseTimeout(r.Header.Get("Timeout"))
if err != nil {
panic(result.BadRequest(err.Error()))
}
li, status, err := webdav.ReadLockInfo(r.Body)
if err != nil {
panic(result.BadRequest(fmt.Sprintf("error:%s, status=%d", err.Error(), status)))
}
token, ld, now, created := "", webdav.LockDetails{}, time.Now(), false
if li == (webdav.LockInfo{}) {
// An empty LockInfo means to refresh the lock.
ih, ok := webdav.ParseIfHeader(r.Header.Get("If"))
if !ok {
panic(result.BadRequest(webdav.ErrInvalidIfHeader.Error()))
}
if len(ih.Lists) == 1 && len(ih.Lists[0].Conditions) == 1 {
token = ih.Lists[0].Conditions[0].Token
}
if token == "" {
panic(result.BadRequest(webdav.ErrInvalidLockToken.Error()))
}
ld, err = this.lockSystem.Refresh(now, token, duration)
if err != nil {
if err == webdav.ErrNoSuchLock {
panic(result.StatusCodeWebResult(http.StatusPreconditionFailed, err.Error()))
}
panic(result.StatusCodeWebResult(http.StatusInternalServerError, err.Error()))
}
} else {
// Section 9.10.3 says that "If no Depth header is submitted on a LOCK request,
// then the request MUST act as if a "Depth:infinity" had been submitted."
depth := webdav.InfiniteDepth
if hdr := r.Header.Get("Depth"); hdr != "" {
depth = webdav.ParseDepth(hdr)
if depth != 0 && depth != webdav.InfiniteDepth {
// Section 9.10.3 says that "Values other than 0 or infinity must not be
// used with the Depth header on a LOCK method".
panic(result.StatusCodeWebResult(http.StatusBadRequest, webdav.ErrInvalidDepth.Error()))
}
}
ld = webdav.LockDetails{
Root: subPath,
Duration: duration,
OwnerXML: li.Owner.InnerXML,
ZeroDepth: depth == 0,
}
token, err = this.lockSystem.Create(now, ld)
if err != nil {
if err == webdav.ErrLocked {
panic(result.StatusCodeWebResult(http.StatusLocked, err.Error()))
}
panic(result.StatusCodeWebResult(http.StatusInternalServerError, err.Error()))
}
defer func() {
//when error occur, rollback.
//this.lockSystem.Unlock(now, token)
}()
// Create the resource if it didn't previously exist.
// ctx := r.Context()
//if _, err := this.FileSystem.Stat(ctx, subPath); err != nil {
// f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
// if err != nil {
// // TODO: detect missing intermediate dirs and return http.StatusConflict?
// return http.StatusInternalServerError, err
// }
// f.Close()
// created = true
//}
// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
// Lock-Token value is a Coded-URL. We add angle brackets.
w.Header().Set("Lock-Token", "<"+token+">")
}
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
if created {
// This is "w.WriteHeader(http.StatusCreated)" and not "return
// http.StatusCreated, nil" because we write our own (XML) response to w
// and Handler.ServeHTTP would otherwise write "Created".
w.WriteHeader(http.StatusCreated)
}
_, _ = webdav.WriteLockInfo(w, token, ld)
panic(result.BadRequest("not support LOCK yet."))
} }
//unlock //unlock

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/eyebluecn/tank/code/tool/i18n" "github.com/eyebluecn/tank/code/tool/i18n"
"net/http" "net/http"
"strconv"
) )
type WebResult struct { type WebResult struct {
@ -73,7 +74,13 @@ func FetchHttpStatus(code string) int {
} else if code == SERVER.Code { } else if code == SERVER.Code {
return SERVER.HttpStatus return SERVER.HttpStatus
} else { } else {
//if this is an int. regard it as statusCode
statusCode, err := strconv.Atoi(code)
if err != nil {
return UNKNOWN.HttpStatus return UNKNOWN.HttpStatus
} else {
return statusCode
}
} }
} }
@ -104,6 +111,18 @@ func CustomWebResult(codeWrapper *CodeWrapper, description string) *WebResult {
return wr return wr
} }
//use standard http status code.
func StatusCodeWebResult(statusCode int, description string) *WebResult {
if description == "" {
description = http.StatusText(statusCode)
}
wr := &WebResult{
Code: fmt.Sprintf("%d", statusCode),
Msg: description,
}
return wr
}
func BadRequestI18n(request *http.Request, item *i18n.Item, v ...interface{}) *WebResult { func BadRequestI18n(request *http.Request, item *i18n.Item, v ...interface{}) *WebResult {
return CustomWebResult(BAD_REQUEST, fmt.Sprintf(item.Message(request), v...)) return CustomWebResult(BAD_REQUEST, fmt.Sprintf(item.Message(request), v...))
} }

View File

@ -370,10 +370,10 @@ func (fs *memFS) Rename(ctx context.Context, oldName, newName string) error {
if oNode.children != nil { if oNode.children != nil {
if nNode, ok := nDir.children[nFrag]; ok { if nNode, ok := nDir.children[nFrag]; ok {
if nNode.children == nil { if nNode.children == nil {
return errNotADirectory return ErrNotADirectory
} }
if len(nNode.children) != 0 { if len(nNode.children) != 0 {
return errDirectoryNotEmpty return ErrDirectoryNotEmpty
} }
} }
} }
@ -653,7 +653,7 @@ func copyProps(dst, src File) error {
// See section 9.8.5 for when various HTTP status codes apply. // See section 9.8.5 for when various HTTP status codes apply.
func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool, depth int, recursion int) (status int, err error) { func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
if recursion == 1000 { if recursion == 1000 {
return http.StatusInternalServerError, errRecursionTooDeep return http.StatusInternalServerError, ErrRecursionTooDeep
} }
recursion++ recursion++

View File

@ -11,39 +11,39 @@ import (
"strings" "strings"
) )
// ifHeader is a disjunction (OR) of ifLists. // IfHeader is a disjunction (OR) of ifLists.
type ifHeader struct { type IfHeader struct {
lists []ifList Lists []IfList
} }
// ifList is a conjunction (AND) of Conditions, and an optional resource tag. // IfList is a conjunction (AND) of Conditions, and an optional resource tag.
type ifList struct { type IfList struct {
resourceTag string ResourceTag string
conditions []Condition Conditions []Condition
} }
// parseIfHeader parses the "If: foo bar" HTTP header. The httpHeader string // 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 // 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. // returned by req.Header.Get("If") for a http.Request req.
func parseIfHeader(httpHeader string) (h ifHeader, ok bool) { func ParseIfHeader(httpHeader string) (h IfHeader, ok bool) {
s := strings.TrimSpace(httpHeader) s := strings.TrimSpace(httpHeader)
switch tokenType, _, _ := lex(s); tokenType { switch tokenType, _, _ := lex(s); tokenType {
case '(': case '(':
return parseNoTagLists(s) return ParseNoTagLists(s)
case angleTokenType: case AngleTokenType:
return parseTaggedLists(s) return ParseTaggedLists(s)
default: default:
return ifHeader{}, false return IfHeader{}, false
} }
} }
func parseNoTagLists(s string) (h ifHeader, ok bool) { func ParseNoTagLists(s string) (h IfHeader, ok bool) {
for { for {
l, remaining, ok := parseList(s) l, remaining, ok := ParseList(s)
if !ok { if !ok {
return ifHeader{}, false return IfHeader{}, false
} }
h.lists = append(h.lists, l) h.Lists = append(h.Lists, l)
if remaining == "" { if remaining == "" {
return h, true return h, true
} }
@ -51,67 +51,67 @@ func parseNoTagLists(s string) (h ifHeader, ok bool) {
} }
} }
func parseTaggedLists(s string) (h ifHeader, ok bool) { func ParseTaggedLists(s string) (h IfHeader, ok bool) {
resourceTag, n := "", 0 resourceTag, n := "", 0
for first := true; ; first = false { for first := true; ; first = false {
tokenType, tokenStr, remaining := lex(s) tokenType, tokenStr, remaining := lex(s)
switch tokenType { switch tokenType {
case angleTokenType: case AngleTokenType:
if !first && n == 0 { if !first && n == 0 {
return ifHeader{}, false return IfHeader{}, false
} }
resourceTag, n = tokenStr, 0 resourceTag, n = tokenStr, 0
s = remaining s = remaining
case '(': case '(':
n++ n++
l, remaining, ok := parseList(s) l, remaining, ok := ParseList(s)
if !ok { if !ok {
return ifHeader{}, false return IfHeader{}, false
} }
l.resourceTag = resourceTag l.ResourceTag = resourceTag
h.lists = append(h.lists, l) h.Lists = append(h.Lists, l)
if remaining == "" { if remaining == "" {
return h, true return h, true
} }
s = remaining s = remaining
default: default:
return ifHeader{}, false return IfHeader{}, false
} }
} }
} }
func parseList(s string) (l ifList, remaining string, ok bool) { func ParseList(s string) (l IfList, remaining string, ok bool) {
tokenType, _, s := lex(s) tokenType, _, s := lex(s)
if tokenType != '(' { if tokenType != '(' {
return ifList{}, "", false return IfList{}, "", false
} }
for { for {
tokenType, _, remaining = lex(s) tokenType, _, remaining = lex(s)
if tokenType == ')' { if tokenType == ')' {
if len(l.conditions) == 0 { if len(l.Conditions) == 0 {
return ifList{}, "", false return IfList{}, "", false
} }
return l, remaining, true return l, remaining, true
} }
c, remaining, ok := parseCondition(s) c, remaining, ok := ParseCondition(s)
if !ok { if !ok {
return ifList{}, "", false return IfList{}, "", false
} }
l.conditions = append(l.conditions, c) l.Conditions = append(l.Conditions, c)
s = remaining s = remaining
} }
} }
func parseCondition(s string) (c Condition, remaining string, ok bool) { func ParseCondition(s string) (c Condition, remaining string, ok bool) {
tokenType, tokenStr, s := lex(s) tokenType, tokenStr, s := lex(s)
if tokenType == notTokenType { if tokenType == NotTokenType {
c.Not = true c.Not = true
tokenType, tokenStr, s = lex(s) tokenType, tokenStr, s = lex(s)
} }
switch tokenType { switch tokenType {
case strTokenType, angleTokenType: case StrTokenType, AngleTokenType:
c.Token = tokenStr c.Token = tokenStr
case squareTokenType: case SquareTokenType:
c.ETag = tokenStr c.ETag = tokenStr
default: default:
return Condition{}, "", false return Condition{}, "", false
@ -122,12 +122,12 @@ func parseCondition(s string) (c Condition, remaining string, ok bool) {
// Single-rune tokens like '(' or ')' have a token type equal to their rune. // Single-rune tokens like '(' or ')' have a token type equal to their rune.
// All other tokens have a negative token type. // All other tokens have a negative token type.
const ( const (
errTokenType = rune(-1) ErrTokenType = rune(-1)
eofTokenType = rune(-2) EofTokenType = rune(-2)
strTokenType = rune(-3) StrTokenType = rune(-3)
notTokenType = rune(-4) NotTokenType = rune(-4)
angleTokenType = rune(-5) AngleTokenType = rune(-5)
squareTokenType = rune(-6) SquareTokenType = rune(-6)
) )
func lex(s string) (tokenType rune, tokenStr string, remaining string) { func lex(s string) (tokenType rune, tokenStr string, remaining string) {
@ -138,7 +138,7 @@ func lex(s string) (tokenType rune, tokenStr string, remaining string) {
s = s[1:] s = s[1:]
} }
if len(s) == 0 { if len(s) == 0 {
return eofTokenType, "", "" return EofTokenType, "", ""
} }
i := 0 i := 0
loop: loop:
@ -152,22 +152,22 @@ loop:
if i != 0 { if i != 0 {
tokenStr, remaining = s[:i], s[i:] tokenStr, remaining = s[:i], s[i:]
if tokenStr == "Not" { if tokenStr == "Not" {
return notTokenType, "", remaining return NotTokenType, "", remaining
} }
return strTokenType, tokenStr, remaining return StrTokenType, tokenStr, remaining
} }
j := 0 j := 0
switch s[0] { switch s[0] {
case '<': case '<':
j, tokenType = strings.IndexByte(s, '>'), angleTokenType j, tokenType = strings.IndexByte(s, '>'), AngleTokenType
case '[': case '[':
j, tokenType = strings.IndexByte(s, ']'), squareTokenType j, tokenType = strings.IndexByte(s, ']'), SquareTokenType
default: default:
return rune(s[0]), "", s[1:] return rune(s[0]), "", s[1:]
} }
if j < 0 { if j < 0 {
return errTokenType, "", "" return ErrTokenType, "", ""
} }
return tokenType, s[1:j], s[j+1:] return tokenType, s[1:j], s[j+1:]
} }

View File

@ -16,61 +16,61 @@ func TestParseIfHeader(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
input string input string
want ifHeader want IfHeader
}{{ }{{
"bad: empty", "bad: empty",
``, ``,
ifHeader{}, IfHeader{},
}, { }, {
"bad: no parens", "bad: no parens",
`foobar`, `foobar`,
ifHeader{}, IfHeader{},
}, { }, {
"bad: empty list #1", "bad: empty list #1",
`()`, `()`,
ifHeader{}, IfHeader{},
}, { }, {
"bad: empty list #2", "bad: empty list #2",
`(a) (b c) () (d)`, `(a) (b c) () (d)`,
ifHeader{}, IfHeader{},
}, { }, {
"bad: no list after resource #1", "bad: no list after resource #1",
`<foo>`, `<foo>`,
ifHeader{}, IfHeader{},
}, { }, {
"bad: no list after resource #2", "bad: no list after resource #2",
`<foo> <bar> (a)`, `<foo> <bar> (a)`,
ifHeader{}, IfHeader{},
}, { }, {
"bad: no list after resource #3", "bad: no list after resource #3",
`<foo> (a) (b) <bar>`, `<foo> (a) (b) <bar>`,
ifHeader{}, IfHeader{},
}, { }, {
"bad: no-tag-list followed by tagged-list", "bad: no-tag-list followed by tagged-list",
`(a) (b) <foo> (c)`, `(a) (b) <foo> (c)`,
ifHeader{}, IfHeader{},
}, { }, {
"bad: unfinished list", "bad: unfinished list",
`(a`, `(a`,
ifHeader{}, IfHeader{},
}, { }, {
"bad: unfinished ETag", "bad: unfinished ETag",
`([b`, `([b`,
ifHeader{}, IfHeader{},
}, { }, {
"bad: unfinished Notted list", "bad: unfinished Notted list",
`(Not a`, `(Not a`,
ifHeader{}, IfHeader{},
}, { }, {
"bad: double Not", "bad: double Not",
`(Not Not a)`, `(Not Not a)`,
ifHeader{}, IfHeader{},
}, { }, {
"good: one list with a Token", "good: one list with a Token",
`(a)`, `(a)`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
conditions: []Condition{{ Conditions: []Condition{{
Token: `a`, Token: `a`,
}}, }},
}}, }},
@ -78,9 +78,9 @@ func TestParseIfHeader(t *testing.T) {
}, { }, {
"good: one list with an ETag", "good: one list with an ETag",
`([a])`, `([a])`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
conditions: []Condition{{ Conditions: []Condition{{
ETag: `a`, ETag: `a`,
}}, }},
}}, }},
@ -88,9 +88,9 @@ func TestParseIfHeader(t *testing.T) {
}, { }, {
"good: one list with three Nots", "good: one list with three Nots",
`(Not a Not b Not [d])`, `(Not a Not b Not [d])`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
conditions: []Condition{{ Conditions: []Condition{{
Not: true, Not: true,
Token: `a`, Token: `a`,
}, { }, {
@ -105,13 +105,13 @@ func TestParseIfHeader(t *testing.T) {
}, { }, {
"good: two lists", "good: two lists",
`(a) (b)`, `(a) (b)`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
conditions: []Condition{{ Conditions: []Condition{{
Token: `a`, Token: `a`,
}}, }},
}, { }, {
conditions: []Condition{{ Conditions: []Condition{{
Token: `b`, Token: `b`,
}}, }},
}}, }},
@ -119,14 +119,14 @@ func TestParseIfHeader(t *testing.T) {
}, { }, {
"good: two Notted lists", "good: two Notted lists",
`(Not a) (Not b)`, `(Not a) (Not b)`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
conditions: []Condition{{ Conditions: []Condition{{
Not: true, Not: true,
Token: `a`, Token: `a`,
}}, }},
}, { }, {
conditions: []Condition{{ Conditions: []Condition{{
Not: true, Not: true,
Token: `b`, Token: `b`,
}}, }},
@ -136,10 +136,10 @@ func TestParseIfHeader(t *testing.T) {
"section 7.5.1", "section 7.5.1",
`<http://www.example.com/users/f/fielding/index.html> `<http://www.example.com/users/f/fielding/index.html>
(<urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6>)`, (<urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6>)`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
resourceTag: `http://www.example.com/users/f/fielding/index.html`, ResourceTag: `http://www.example.com/users/f/fielding/index.html`,
conditions: []Condition{{ Conditions: []Condition{{
Token: `urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6`, Token: `urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6`,
}}, }},
}}, }},
@ -147,9 +147,9 @@ func TestParseIfHeader(t *testing.T) {
}, { }, {
"section 7.5.2 #1", "section 7.5.2 #1",
`(<urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf>)`, `(<urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf>)`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
conditions: []Condition{{ Conditions: []Condition{{
Token: `urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf`, Token: `urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf`,
}}, }},
}}, }},
@ -158,10 +158,10 @@ func TestParseIfHeader(t *testing.T) {
"section 7.5.2 #2", "section 7.5.2 #2",
`<http://example.com/locked/> `<http://example.com/locked/>
(<urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf>)`, (<urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf>)`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
resourceTag: `http://example.com/locked/`, ResourceTag: `http://example.com/locked/`,
conditions: []Condition{{ Conditions: []Condition{{
Token: `urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf`, Token: `urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf`,
}}, }},
}}, }},
@ -170,10 +170,10 @@ func TestParseIfHeader(t *testing.T) {
"section 7.5.2 #3", "section 7.5.2 #3",
`<http://example.com/locked/member> `<http://example.com/locked/member>
(<urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf>)`, (<urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf>)`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
resourceTag: `http://example.com/locked/member`, ResourceTag: `http://example.com/locked/member`,
conditions: []Condition{{ Conditions: []Condition{{
Token: `urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf`, Token: `urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf`,
}}, }},
}}, }},
@ -182,13 +182,13 @@ func TestParseIfHeader(t *testing.T) {
"section 9.9.6", "section 9.9.6",
`(<urn:uuid:fe184f2e-6eec-41d0-c765-01adc56e6bb4>) `(<urn:uuid:fe184f2e-6eec-41d0-c765-01adc56e6bb4>)
(<urn:uuid:e454f3f3-acdc-452a-56c7-00a5c91e4b77>)`, (<urn:uuid:e454f3f3-acdc-452a-56c7-00a5c91e4b77>)`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
conditions: []Condition{{ Conditions: []Condition{{
Token: `urn:uuid:fe184f2e-6eec-41d0-c765-01adc56e6bb4`, Token: `urn:uuid:fe184f2e-6eec-41d0-c765-01adc56e6bb4`,
}}, }},
}, { }, {
conditions: []Condition{{ Conditions: []Condition{{
Token: `urn:uuid:e454f3f3-acdc-452a-56c7-00a5c91e4b77`, Token: `urn:uuid:e454f3f3-acdc-452a-56c7-00a5c91e4b77`,
}}, }},
}}, }},
@ -196,9 +196,9 @@ func TestParseIfHeader(t *testing.T) {
}, { }, {
"section 9.10.8", "section 9.10.8",
`(<urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4>)`, `(<urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4>)`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
conditions: []Condition{{ Conditions: []Condition{{
Token: `urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4`, Token: `urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4`,
}}, }},
}}, }},
@ -208,15 +208,15 @@ func TestParseIfHeader(t *testing.T) {
`(<urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2> `(<urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>
["I am an ETag"]) ["I am an ETag"])
(["I am another ETag"])`, (["I am another ETag"])`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
conditions: []Condition{{ Conditions: []Condition{{
Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`, Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`,
}, { }, {
ETag: `"I am an ETag"`, ETag: `"I am an ETag"`,
}}, }},
}, { }, {
conditions: []Condition{{ Conditions: []Condition{{
ETag: `"I am another ETag"`, ETag: `"I am another ETag"`,
}}, }},
}}, }},
@ -225,9 +225,9 @@ func TestParseIfHeader(t *testing.T) {
"section 10.4.7", "section 10.4.7",
`(Not <urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2> `(Not <urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>
<urn:uuid:58f202ac-22cf-11d1-b12d-002035b29092>)`, <urn:uuid:58f202ac-22cf-11d1-b12d-002035b29092>)`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
conditions: []Condition{{ Conditions: []Condition{{
Not: true, Not: true,
Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`, Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`,
}, { }, {
@ -239,13 +239,13 @@ func TestParseIfHeader(t *testing.T) {
"section 10.4.8", "section 10.4.8",
`(<urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>) `(<urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>)
(Not <DAV:no-lock>)`, (Not <DAV:no-lock>)`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
conditions: []Condition{{ Conditions: []Condition{{
Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`, Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`,
}}, }},
}, { }, {
conditions: []Condition{{ Conditions: []Condition{{
Not: true, Not: true,
Token: `DAV:no-lock`, Token: `DAV:no-lock`,
}}, }},
@ -256,17 +256,17 @@ func TestParseIfHeader(t *testing.T) {
`</resource1> `</resource1>
(<urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2> (<urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>
[W/"A weak ETag"]) (["strong ETag"])`, [W/"A weak ETag"]) (["strong ETag"])`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
resourceTag: `/resource1`, ResourceTag: `/resource1`,
conditions: []Condition{{ Conditions: []Condition{{
Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`, Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`,
}, { }, {
ETag: `W/"A weak ETag"`, ETag: `W/"A weak ETag"`,
}}, }},
}, { }, {
resourceTag: `/resource1`, ResourceTag: `/resource1`,
conditions: []Condition{{ Conditions: []Condition{{
ETag: `"strong ETag"`, ETag: `"strong ETag"`,
}}, }},
}}, }},
@ -275,10 +275,10 @@ func TestParseIfHeader(t *testing.T) {
"section 10.4.10", "section 10.4.10",
`<http://www.example.com/specs/> `<http://www.example.com/specs/>
(<urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>)`, (<urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>)`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
resourceTag: `http://www.example.com/specs/`, ResourceTag: `http://www.example.com/specs/`,
conditions: []Condition{{ Conditions: []Condition{{
Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`, Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`,
}}, }},
}}, }},
@ -286,10 +286,10 @@ func TestParseIfHeader(t *testing.T) {
}, { }, {
"section 10.4.11 #1", "section 10.4.11 #1",
`</specs/rfc2518.doc> (["4217"])`, `</specs/rfc2518.doc> (["4217"])`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
resourceTag: `/specs/rfc2518.doc`, ResourceTag: `/specs/rfc2518.doc`,
conditions: []Condition{{ Conditions: []Condition{{
ETag: `"4217"`, ETag: `"4217"`,
}}, }},
}}, }},
@ -297,10 +297,10 @@ func TestParseIfHeader(t *testing.T) {
}, { }, {
"section 10.4.11 #2", "section 10.4.11 #2",
`</specs/rfc2518.doc> (Not ["4217"])`, `</specs/rfc2518.doc> (Not ["4217"])`,
ifHeader{ IfHeader{
lists: []ifList{{ Lists: []IfList{{
resourceTag: `/specs/rfc2518.doc`, ResourceTag: `/specs/rfc2518.doc`,
conditions: []Condition{{ Conditions: []Condition{{
Not: true, Not: true,
ETag: `"4217"`, ETag: `"4217"`,
}}, }},
@ -309,8 +309,8 @@ func TestParseIfHeader(t *testing.T) {
}} }}
for _, tc := range testCases { for _, tc := range testCases {
got, ok := parseIfHeader(strings.Replace(tc.input, "\n", "", -1)) got, ok := ParseIfHeader(strings.Replace(tc.input, "\n", "", -1))
if gotEmpty := reflect.DeepEqual(got, ifHeader{}); gotEmpty == ok { if gotEmpty := reflect.DeepEqual(got, IfHeader{}); gotEmpty == ok {
t.Errorf("%s: should be different: empty header == %t, ok == %t", tc.desc, gotEmpty, ok) t.Errorf("%s: should be different: empty header == %t, ok == %t", tc.desc, gotEmpty, ok)
continue continue
} }

View File

@ -37,7 +37,7 @@ type Condition struct {
// of host operating system convention. // of host operating system convention.
type LockSystem interface { type LockSystem interface {
// Confirm confirms that the caller can claim all of the locks specified by // Confirm confirms that the caller can claim all of the locks specified by
// the given conditions, and that holding the union of all of those locks // the given Conditions, and that holding the union of all of those locks
// gives exclusive access to all of the named resources. Up to two resources // gives exclusive access to all of the named resources. Up to two resources
// can be named. Empty names are ignored. // can be named. Empty names are ignored.
// //
@ -113,14 +113,14 @@ type LockDetails struct {
// NewMemLS returns a new in-memory LockSystem. // NewMemLS returns a new in-memory LockSystem.
func NewMemLS() LockSystem { func NewMemLS() LockSystem {
return &memLS{ return &MemLS{
byName: make(map[string]*memLSNode), byName: make(map[string]*memLSNode),
byToken: make(map[string]*memLSNode), byToken: make(map[string]*memLSNode),
gen: uint64(time.Now().Unix()), gen: uint64(time.Now().Unix()),
} }
} }
type memLS struct { type MemLS struct {
mu sync.Mutex mu sync.Mutex
byName map[string]*memLSNode byName map[string]*memLSNode
byToken map[string]*memLSNode byToken map[string]*memLSNode
@ -130,12 +130,12 @@ type memLS struct {
byExpiry byExpiry byExpiry byExpiry
} }
func (m *memLS) nextToken() string { func (m *MemLS) nextToken() string {
m.gen++ m.gen++
return strconv.FormatUint(m.gen, 10) return strconv.FormatUint(m.gen, 10)
} }
func (m *memLS) collectExpiredNodes(now time.Time) { func (m *MemLS) collectExpiredNodes(now time.Time) {
for len(m.byExpiry) > 0 { for len(m.byExpiry) > 0 {
if now.Before(m.byExpiry[0].expiry) { if now.Before(m.byExpiry[0].expiry) {
break break
@ -144,7 +144,7 @@ func (m *memLS) collectExpiredNodes(now time.Time) {
} }
} }
func (m *memLS) Confirm(now time.Time, name0, name1 string, conditions ...Condition) (func(), error) { func (m *MemLS) Confirm(now time.Time, name0, name1 string, conditions ...Condition) (func(), error) {
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
m.collectExpiredNodes(now) m.collectExpiredNodes(now)
@ -185,11 +185,11 @@ func (m *memLS) Confirm(now time.Time, name0, name1 string, conditions ...Condit
} }
// lookup returns the node n that locks the named resource, provided that n // lookup returns the node n that locks the named resource, provided that n
// matches at least one of the given conditions and that lock isn't held by // matches at least one of the given Conditions and that lock isn't held by
// another party. Otherwise, it returns nil. // another party. Otherwise, it returns nil.
// //
// n may be a parent of the named resource, if n is an infinite depth lock. // n may be a parent of the named resource, if n is an infinite depth lock.
func (m *memLS) lookup(name string, conditions ...Condition) (n *memLSNode) { func (m *MemLS) lookup(name string, conditions ...Condition) (n *memLSNode) {
// TODO: support Condition.Not and Condition.ETag. // TODO: support Condition.Not and Condition.ETag.
for _, c := range conditions { for _, c := range conditions {
n = m.byToken[c.Token] n = m.byToken[c.Token]
@ -209,9 +209,9 @@ func (m *memLS) lookup(name string, conditions ...Condition) (n *memLSNode) {
return nil return nil
} }
func (m *memLS) hold(n *memLSNode) { func (m *MemLS) hold(n *memLSNode) {
if n.held { if n.held {
panic("webdav: memLS inconsistent held state") panic("webdav: MemLS inconsistent held state")
} }
n.held = true n.held = true
if n.details.Duration >= 0 && n.byExpiryIndex >= 0 { if n.details.Duration >= 0 && n.byExpiryIndex >= 0 {
@ -219,9 +219,9 @@ func (m *memLS) hold(n *memLSNode) {
} }
} }
func (m *memLS) unhold(n *memLSNode) { func (m *MemLS) unhold(n *memLSNode) {
if !n.held { if !n.held {
panic("webdav: memLS inconsistent held state") panic("webdav: MemLS inconsistent held state")
} }
n.held = false n.held = false
if n.details.Duration >= 0 { if n.details.Duration >= 0 {
@ -229,7 +229,7 @@ func (m *memLS) unhold(n *memLSNode) {
} }
} }
func (m *memLS) Create(now time.Time, details LockDetails) (string, error) { func (m *MemLS) Create(now time.Time, details LockDetails) (string, error) {
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
m.collectExpiredNodes(now) m.collectExpiredNodes(now)
@ -249,7 +249,7 @@ func (m *memLS) Create(now time.Time, details LockDetails) (string, error) {
return n.token, nil return n.token, nil
} }
func (m *memLS) Refresh(now time.Time, token string, duration time.Duration) (LockDetails, error) { func (m *MemLS) Refresh(now time.Time, token string, duration time.Duration) (LockDetails, error) {
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
m.collectExpiredNodes(now) m.collectExpiredNodes(now)
@ -272,7 +272,7 @@ func (m *memLS) Refresh(now time.Time, token string, duration time.Duration) (Lo
return n.details, nil return n.details, nil
} }
func (m *memLS) Unlock(now time.Time, token string) error { func (m *MemLS) Unlock(now time.Time, token string) error {
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
m.collectExpiredNodes(now) m.collectExpiredNodes(now)
@ -288,7 +288,7 @@ func (m *memLS) Unlock(now time.Time, token string) error {
return nil return nil
} }
func (m *memLS) canCreate(name string, zeroDepth bool) bool { func (m *MemLS) canCreate(name string, zeroDepth bool) bool {
return walkToRoot(name, func(name0 string, first bool) bool { return walkToRoot(name, func(name0 string, first bool) bool {
n := m.byName[name0] n := m.byName[name0]
if n == nil { if n == nil {
@ -312,7 +312,7 @@ func (m *memLS) canCreate(name string, zeroDepth bool) bool {
}) })
} }
func (m *memLS) create(name string) (ret *memLSNode) { func (m *MemLS) create(name string) (ret *memLSNode) {
walkToRoot(name, func(name0 string, first bool) bool { walkToRoot(name, func(name0 string, first bool) bool {
n := m.byName[name0] n := m.byName[name0]
if n == nil { if n == nil {
@ -333,7 +333,7 @@ func (m *memLS) create(name string) (ret *memLSNode) {
return ret return ret
} }
func (m *memLS) remove(n *memLSNode) { func (m *MemLS) remove(n *memLSNode) {
delete(m.byToken, n.token) delete(m.byToken, n.token)
n.token = "" n.token = ""
walkToRoot(n.details.Root, func(name0 string, first bool) bool { walkToRoot(n.details.Root, func(name0 string, first bool) bool {
@ -376,7 +376,7 @@ type memLSNode struct {
refCount int refCount int
// expiry is when this node's lock expires. // expiry is when this node's lock expires.
expiry time.Time expiry time.Time
// byExpiryIndex is the index of this node in memLS.byExpiry. It is -1 // byExpiryIndex is the index of this node in MemLS.byExpiry. It is -1
// if this node does not expire, or has expired. // if this node does not expire, or has expired.
byExpiryIndex int byExpiryIndex int
// held is whether this node's lock is actively held by a Confirm call. // held is whether this node's lock is actively held by a Confirm call.
@ -416,9 +416,9 @@ func (b *byExpiry) Pop() interface{} {
const infiniteTimeout = -1 const infiniteTimeout = -1
// parseTimeout parses the Timeout HTTP header, as per section 10.7. If s is // ParseTimeout parses the Timeout HTTP header, as per section 10.7. If s is
// empty, an infiniteTimeout is returned. // empty, an infiniteTimeout is returned.
func parseTimeout(s string) (time.Duration, error) { func ParseTimeout(s string) (time.Duration, error) {
if s == "" { if s == "" {
return infiniteTimeout, nil return infiniteTimeout, nil
} }
@ -431,15 +431,15 @@ func parseTimeout(s string) (time.Duration, error) {
} }
const pre = "Second-" const pre = "Second-"
if !strings.HasPrefix(s, pre) { if !strings.HasPrefix(s, pre) {
return 0, errInvalidTimeout return 0, ErrInvalidTimeout
} }
s = s[len(pre):] s = s[len(pre):]
if s == "" || s[0] < '0' || '9' < s[0] { if s == "" || s[0] < '0' || '9' < s[0] {
return 0, errInvalidTimeout return 0, ErrInvalidTimeout
} }
n, err := strconv.ParseInt(s, 10, 64) n, err := strconv.ParseInt(s, 10, 64)
if err != nil || 1<<32-1 < n { if err != nil || 1<<32-1 < n {
return 0, errInvalidTimeout return 0, ErrInvalidTimeout
} }
return time.Duration(n) * time.Second, nil return time.Duration(n) * time.Second, nil
} }

View File

@ -97,7 +97,7 @@ func lockTestZeroDepth(name string) bool {
func TestMemLSCanCreate(t *testing.T) { func TestMemLSCanCreate(t *testing.T) {
now := time.Unix(0, 0) now := time.Unix(0, 0)
m := NewMemLS().(*memLS) m := NewMemLS().(*MemLS)
for _, name := range lockTestNames { for _, name := range lockTestNames {
_, err := m.Create(now, LockDetails{ _, err := m.Create(now, LockDetails{
@ -157,7 +157,7 @@ func TestMemLSCanCreate(t *testing.T) {
func TestMemLSLookup(t *testing.T) { func TestMemLSLookup(t *testing.T) {
now := time.Unix(0, 0) now := time.Unix(0, 0)
m := NewMemLS().(*memLS) m := NewMemLS().(*MemLS)
badToken := m.nextToken() badToken := m.nextToken()
t.Logf("badToken=%q", badToken) t.Logf("badToken=%q", badToken)
@ -206,7 +206,7 @@ func TestMemLSLookup(t *testing.T) {
func TestMemLSConfirm(t *testing.T) { func TestMemLSConfirm(t *testing.T) {
now := time.Unix(0, 0) now := time.Unix(0, 0)
m := NewMemLS().(*memLS) m := NewMemLS().(*MemLS)
alice, err := m.Create(now, LockDetails{ alice, err := m.Create(now, LockDetails{
Root: "/alice", Root: "/alice",
Duration: infiniteTimeout, Duration: infiniteTimeout,
@ -302,7 +302,7 @@ func TestMemLSConfirm(t *testing.T) {
func TestMemLSNonCanonicalRoot(t *testing.T) { func TestMemLSNonCanonicalRoot(t *testing.T) {
now := time.Unix(0, 0) now := time.Unix(0, 0)
m := NewMemLS().(*memLS) m := NewMemLS().(*MemLS)
token, err := m.Create(now, LockDetails{ token, err := m.Create(now, LockDetails{
Root: "/foo/./bar//", Root: "/foo/./bar//",
Duration: 1 * time.Second, Duration: 1 * time.Second,
@ -322,7 +322,7 @@ func TestMemLSNonCanonicalRoot(t *testing.T) {
} }
func TestMemLSExpiry(t *testing.T) { func TestMemLSExpiry(t *testing.T) {
m := NewMemLS().(*memLS) m := NewMemLS().(*MemLS)
testCases := []string{ testCases := []string{
"setNow 0", "setNow 0",
"create /a.5", "create /a.5",
@ -451,7 +451,7 @@ func TestMemLSExpiry(t *testing.T) {
func TestMemLS(t *testing.T) { func TestMemLS(t *testing.T) {
now := time.Unix(0, 0) now := time.Unix(0, 0)
m := NewMemLS().(*memLS) m := NewMemLS().(*MemLS)
rng := rand.New(rand.NewSource(0)) rng := rand.New(rand.NewSource(0))
tokens := map[string]string{} tokens := map[string]string{}
nConfirm, nCreate, nRefresh, nUnlock := 0, 0, 0, 0 nConfirm, nCreate, nRefresh, nUnlock := 0, 0, 0, 0
@ -535,7 +535,7 @@ func TestMemLS(t *testing.T) {
} }
} }
func (m *memLS) consistent() error { func (m *MemLS) consistent() error {
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
@ -653,11 +653,11 @@ func TestParseTimeout(t *testing.T) {
}, { }, {
"Infinitesimal", "Infinitesimal",
0, 0,
errInvalidTimeout, ErrInvalidTimeout,
}, { }, {
"infinite", "infinite",
0, 0,
errInvalidTimeout, ErrInvalidTimeout,
}, { }, {
"Second-0", "Second-0",
0 * time.Second, 0 * time.Second,
@ -677,31 +677,31 @@ func TestParseTimeout(t *testing.T) {
}, { }, {
"junk", "junk",
0, 0,
errInvalidTimeout, ErrInvalidTimeout,
}, { }, {
"Second-", "Second-",
0, 0,
errInvalidTimeout, ErrInvalidTimeout,
}, { }, {
"Second--1", "Second--1",
0, 0,
errInvalidTimeout, ErrInvalidTimeout,
}, { }, {
"Second--123", "Second--123",
0, 0,
errInvalidTimeout, ErrInvalidTimeout,
}, { }, {
"Second-+123", "Second-+123",
0, 0,
errInvalidTimeout, ErrInvalidTimeout,
}, { }, {
"Second-0x123", "Second-0x123",
0, 0,
errInvalidTimeout, ErrInvalidTimeout,
}, { }, {
"second-123", "second-123",
0, 0,
errInvalidTimeout, ErrInvalidTimeout,
}, { }, {
"Second-4294967295", "Second-4294967295",
4294967295 * time.Second, 4294967295 * time.Second,
@ -711,7 +711,7 @@ func TestParseTimeout(t *testing.T) {
// must not be greater than 2^32-1." // must not be greater than 2^32-1."
"Second-4294967296", "Second-4294967296",
0, 0,
errInvalidTimeout, ErrInvalidTimeout,
}, { }, {
// This test case comes from section 9.10.9 of the spec. It says, // This test case comes from section 9.10.9 of the spec. It says,
// //
@ -727,7 +727,7 @@ func TestParseTimeout(t *testing.T) {
}} }}
for _, tc := range testCases { for _, tc := range testCases {
got, gotErr := parseTimeout(tc.s) got, gotErr := ParseTimeout(tc.s)
if got != tc.want || gotErr != tc.wantErr { if got != tc.want || gotErr != tc.wantErr {
t.Errorf("parsing %q:\ngot %v, %v\nwant %v, %v", tc.s, got, gotErr, tc.want, tc.wantErr) t.Errorf("parsing %q:\ngot %v, %v\nwant %v, %v", tc.s, got, gotErr, tc.want, tc.wantErr)
} }

View File

@ -36,15 +36,15 @@ func (h *Handler) stripPrefix(p string) (string, int, error) {
if r := strings.TrimPrefix(p, h.Prefix); len(r) < len(p) { if r := strings.TrimPrefix(p, h.Prefix); len(r) < len(p) {
return r, http.StatusOK, nil return r, http.StatusOK, nil
} }
return p, http.StatusNotFound, errPrefixMismatch return p, http.StatusNotFound, ErrPrefixMismatch
} }
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
status, err := http.StatusBadRequest, errUnsupportedMethod status, err := http.StatusBadRequest, ErrUnsupportedMethod
if h.FileSystem == nil { if h.FileSystem == nil {
status, err = http.StatusInternalServerError, errNoFileSystem status, err = http.StatusInternalServerError, ErrNoFileSystem
} else if h.LockSystem == nil { } else if h.LockSystem == nil {
status, err = http.StatusInternalServerError, errNoLockSystem status, err = http.StatusInternalServerError, ErrNoLockSystem
} else { } else {
switch r.Method { switch r.Method {
case "OPTIONS": case "OPTIONS":
@ -131,13 +131,13 @@ func (h *Handler) confirmLocks(r *http.Request, src, dst string) (release func()
}, 0, nil }, 0, nil
} }
ih, ok := parseIfHeader(hdr) ih, ok := ParseIfHeader(hdr)
if !ok { if !ok {
return nil, http.StatusBadRequest, errInvalidIfHeader return nil, http.StatusBadRequest, ErrInvalidIfHeader
} }
// ih is a disjunction (OR) of ifLists, so any ifList will do. // ih is a disjunction (OR) of ifLists, so any IfList will do.
for _, l := range ih.lists { for _, l := range ih.Lists {
lsrc := l.resourceTag lsrc := l.ResourceTag
if lsrc == "" { if lsrc == "" {
lsrc = src lsrc = src
} else { } else {
@ -153,7 +153,7 @@ func (h *Handler) confirmLocks(r *http.Request, src, dst string) (release func()
return nil, status, err return nil, status, err
} }
} }
release, err = h.LockSystem.Confirm(time.Now(), lsrc, dst, l.conditions...) release, err = h.LockSystem.Confirm(time.Now(), lsrc, dst, l.Conditions...)
if err == ErrConfirmationFailed { if err == ErrConfirmationFailed {
continue continue
} }
@ -317,14 +317,14 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status in
func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status int, err error) { func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status int, err error) {
hdr := r.Header.Get("Destination") hdr := r.Header.Get("Destination")
if hdr == "" { if hdr == "" {
return http.StatusBadRequest, errInvalidDestination return http.StatusBadRequest, ErrInvalidDestination
} }
u, err := url.Parse(hdr) u, err := url.Parse(hdr)
if err != nil { if err != nil {
return http.StatusBadRequest, errInvalidDestination return http.StatusBadRequest, ErrInvalidDestination
} }
if u.Host != "" && u.Host != r.Host { if u.Host != "" && u.Host != r.Host {
return http.StatusBadGateway, errInvalidDestination return http.StatusBadGateway, ErrInvalidDestination
} }
src, status, err := h.stripPrefix(r.URL.Path) src, status, err := h.stripPrefix(r.URL.Path)
@ -338,10 +338,10 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
} }
if dst == "" { if dst == "" {
return http.StatusBadGateway, errInvalidDestination return http.StatusBadGateway, ErrInvalidDestination
} }
if dst == src { if dst == src {
return http.StatusForbidden, errDestinationEqualsSource return http.StatusForbidden, ErrDestinationEqualsSource
} }
ctx := r.Context() ctx := r.Context()
@ -366,7 +366,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
if depth != 0 && depth != InfiniteDepth { if depth != 0 && depth != InfiniteDepth {
// Section 9.8.3 says that "A client may submit a Depth header on a // Section 9.8.3 says that "A client may submit a Depth header on a
// COPY on a collection with a value of "0" or "infinity"." // COPY on a collection with a value of "0" or "infinity"."
return http.StatusBadRequest, errInvalidDepth return http.StatusBadRequest, ErrInvalidDepth
} }
} }
return copyFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") != "F", depth, 0) return copyFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") != "F", depth, 0)
@ -383,35 +383,35 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
// Depth header on a MOVE on a collection with any value but "infinity"." // Depth header on a MOVE on a collection with any value but "infinity"."
if hdr := r.Header.Get("Depth"); hdr != "" { if hdr := r.Header.Get("Depth"); hdr != "" {
if ParseDepth(hdr) != InfiniteDepth { if ParseDepth(hdr) != InfiniteDepth {
return http.StatusBadRequest, errInvalidDepth return http.StatusBadRequest, ErrInvalidDepth
} }
} }
return moveFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") == "T") return moveFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") == "T")
} }
func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus int, retErr error) { func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus int, retErr error) {
duration, err := parseTimeout(r.Header.Get("Timeout")) duration, err := ParseTimeout(r.Header.Get("Timeout"))
if err != nil { if err != nil {
return http.StatusBadRequest, err return http.StatusBadRequest, err
} }
li, status, err := readLockInfo(r.Body) li, status, err := ReadLockInfo(r.Body)
if err != nil { if err != nil {
return status, err return status, err
} }
ctx := r.Context() ctx := r.Context()
token, ld, now, created := "", LockDetails{}, time.Now(), false token, ld, now, created := "", LockDetails{}, time.Now(), false
if li == (lockInfo{}) { if li == (LockInfo{}) {
// An empty lockInfo means to refresh the lock. // An empty LockInfo means to refresh the lock.
ih, ok := parseIfHeader(r.Header.Get("If")) ih, ok := ParseIfHeader(r.Header.Get("If"))
if !ok { if !ok {
return http.StatusBadRequest, errInvalidIfHeader return http.StatusBadRequest, ErrInvalidIfHeader
} }
if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 { if len(ih.Lists) == 1 && len(ih.Lists[0].Conditions) == 1 {
token = ih.lists[0].conditions[0].Token token = ih.Lists[0].Conditions[0].Token
} }
if token == "" { if token == "" {
return http.StatusBadRequest, errInvalidLockToken return http.StatusBadRequest, ErrInvalidLockToken
} }
ld, err = h.LockSystem.Refresh(now, token, duration) ld, err = h.LockSystem.Refresh(now, token, duration)
if err != nil { if err != nil {
@ -430,7 +430,7 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus
if depth != 0 && depth != InfiniteDepth { if depth != 0 && depth != InfiniteDepth {
// Section 9.10.3 says that "Values other than 0 or infinity must not be // Section 9.10.3 says that "Values other than 0 or infinity must not be
// used with the Depth header on a LOCK method". // used with the Depth header on a LOCK method".
return http.StatusBadRequest, errInvalidDepth return http.StatusBadRequest, ErrInvalidDepth
} }
} }
reqPath, status, err := h.stripPrefix(r.URL.Path) reqPath, status, err := h.stripPrefix(r.URL.Path)
@ -479,7 +479,7 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus
// and Handler.ServeHTTP would otherwise write "Created". // and Handler.ServeHTTP would otherwise write "Created".
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
} }
writeLockInfo(w, token, ld) _, _ = WriteLockInfo(w, token, ld)
return 0, nil return 0, nil
} }
@ -488,7 +488,7 @@ func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request) (status i
// Lock-Token value is a Coded-URL. We strip its angle brackets. // Lock-Token value is a Coded-URL. We strip its angle brackets.
t := r.Header.Get("Lock-Token") t := r.Header.Get("Lock-Token")
if len(t) < 2 || t[0] != '<' || t[len(t)-1] != '>' { if len(t) < 2 || t[0] != '<' || t[len(t)-1] != '>' {
return http.StatusBadRequest, errInvalidLockToken return http.StatusBadRequest, ErrInvalidLockToken
} }
t = t[1 : len(t)-1] t = t[1 : len(t)-1]
@ -523,7 +523,7 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
if hdr := r.Header.Get("Depth"); hdr != "" { if hdr := r.Header.Get("Depth"); hdr != "" {
depth = ParseDepth(hdr) depth = ParseDepth(hdr)
if depth == InvalidDepth { if depth == InvalidDepth {
return http.StatusBadRequest, errInvalidDepth return http.StatusBadRequest, ErrInvalidDepth
} }
} }
pf, status, err := ReadPropfind(r.Body) pf, status, err := ReadPropfind(r.Body)
@ -685,22 +685,22 @@ func StatusText(code int) string {
} }
var ( var (
errDestinationEqualsSource = errors.New("webdav: destination equals source") ErrDestinationEqualsSource = errors.New("webdav: destination equals source")
errDirectoryNotEmpty = errors.New("webdav: directory not empty") ErrDirectoryNotEmpty = errors.New("webdav: directory not empty")
errInvalidDepth = errors.New("webdav: invalid depth") ErrInvalidDepth = errors.New("webdav: invalid depth")
errInvalidDestination = errors.New("webdav: invalid destination") ErrInvalidDestination = errors.New("webdav: invalid destination")
errInvalidIfHeader = errors.New("webdav: invalid If header") ErrInvalidIfHeader = errors.New("webdav: invalid If header")
errInvalidLockInfo = errors.New("webdav: invalid lock info") ErrInvalidLockInfo = errors.New("webdav: invalid lock info")
errInvalidLockToken = errors.New("webdav: invalid lock token") ErrInvalidLockToken = errors.New("webdav: invalid lock token")
errInvalidPropfind = errors.New("webdav: invalid propfind") ErrInvalidPropfind = errors.New("webdav: invalid propfind")
errInvalidProppatch = errors.New("webdav: invalid proppatch") ErrInvalidProppatch = errors.New("webdav: invalid proppatch")
errInvalidResponse = errors.New("webdav: invalid response") ErrInvalidResponse = errors.New("webdav: invalid response")
errInvalidTimeout = errors.New("webdav: invalid timeout") ErrInvalidTimeout = errors.New("webdav: invalid timeout")
errNoFileSystem = errors.New("webdav: no file system") ErrNoFileSystem = errors.New("webdav: no file system")
errNoLockSystem = errors.New("webdav: no lock system") ErrNoLockSystem = errors.New("webdav: no lock system")
errNotADirectory = errors.New("webdav: not a directory") ErrNotADirectory = errors.New("webdav: not a directory")
errPrefixMismatch = errors.New("webdav: prefix mismatch") ErrPrefixMismatch = errors.New("webdav: prefix mismatch")
errRecursionTooDeep = errors.New("webdav: recursion too deep") ErrRecursionTooDeep = errors.New("webdav: recursion too deep")
errUnsupportedLockInfo = errors.New("webdav: unsupported lock info") ErrUnsupportedLockInfo = errors.New("webdav: unsupported lock info")
errUnsupportedMethod = errors.New("webdav: unsupported method") ErrUnsupportedMethod = errors.New("webdav: unsupported method")
) )

View File

@ -36,7 +36,7 @@ import (
) )
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockinfo // http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockinfo
type lockInfo struct { type LockInfo struct {
XMLName ixml.Name `xml:"lockinfo"` XMLName ixml.Name `xml:"lockinfo"`
Exclusive *struct{} `xml:"lockscope>exclusive"` Exclusive *struct{} `xml:"lockscope>exclusive"`
Shared *struct{} `xml:"lockscope>shared"` Shared *struct{} `xml:"lockscope>shared"`
@ -49,23 +49,23 @@ type owner struct {
InnerXML string `xml:",innerxml"` InnerXML string `xml:",innerxml"`
} }
func readLockInfo(r io.Reader) (li lockInfo, status int, err error) { func ReadLockInfo(r io.Reader) (li LockInfo, status int, err error) {
c := &countingReader{r: r} c := &countingReader{r: r}
if err = ixml.NewDecoder(c).Decode(&li); err != nil { if err = ixml.NewDecoder(c).Decode(&li); err != nil {
if err == io.EOF { if err == io.EOF {
if c.n == 0 { if c.n == 0 {
// An empty body means to refresh the lock. // An empty body means to refresh the lock.
// http://www.webdav.org/specs/rfc4918.html#refreshing-locks // http://www.webdav.org/specs/rfc4918.html#refreshing-locks
return lockInfo{}, 0, nil return LockInfo{}, 0, nil
} }
err = errInvalidLockInfo err = ErrInvalidLockInfo
} }
return lockInfo{}, http.StatusBadRequest, err return LockInfo{}, http.StatusBadRequest, err
} }
// We only support exclusive (non-shared) write locks. In practice, these are // We only support exclusive (non-shared) write locks. In practice, these are
// the only types of locks that seem to matter. // the only types of locks that seem to matter.
if li.Exclusive == nil || li.Shared != nil || li.Write == nil { if li.Exclusive == nil || li.Shared != nil || li.Write == nil {
return lockInfo{}, http.StatusNotImplemented, errUnsupportedLockInfo return LockInfo{}, http.StatusNotImplemented, ErrUnsupportedLockInfo
} }
return li, 0, nil return li, 0, nil
} }
@ -81,7 +81,7 @@ func (c *countingReader) Read(p []byte) (int, error) {
return n, err return n, err
} }
func writeLockInfo(w io.Writer, token string, ld LockDetails) (int, error) { func WriteLockInfo(w io.Writer, token string, ld LockDetails) (int, error) {
depth := "infinity" depth := "infinity"
if ld.ZeroDepth { if ld.ZeroDepth {
depth = "0" depth = "0"
@ -184,22 +184,22 @@ func ReadPropfind(r io.Reader) (pf Propfind, status int, err error) {
// http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND // http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
return Propfind{Allprop: new(struct{})}, 0, nil return Propfind{Allprop: new(struct{})}, 0, nil
} }
err = errInvalidPropfind err = ErrInvalidPropfind
} }
return Propfind{}, http.StatusBadRequest, err return Propfind{}, http.StatusBadRequest, err
} }
if pf.Allprop == nil && pf.Include != nil { if pf.Allprop == nil && pf.Include != nil {
return Propfind{}, http.StatusBadRequest, errInvalidPropfind return Propfind{}, http.StatusBadRequest, ErrInvalidPropfind
} }
if pf.Allprop != nil && (pf.Prop != nil || pf.Propname != nil) { if pf.Allprop != nil && (pf.Prop != nil || pf.Propname != nil) {
return Propfind{}, http.StatusBadRequest, errInvalidPropfind return Propfind{}, http.StatusBadRequest, ErrInvalidPropfind
} }
if pf.Prop != nil && pf.Propname != nil { if pf.Prop != nil && pf.Propname != nil {
return Propfind{}, http.StatusBadRequest, errInvalidPropfind return Propfind{}, http.StatusBadRequest, ErrInvalidPropfind
} }
if pf.Propname == nil && pf.Allprop == nil && pf.Prop == nil { if pf.Propname == nil && pf.Allprop == nil && pf.Prop == nil {
return Propfind{}, http.StatusBadRequest, errInvalidPropfind return Propfind{}, http.StatusBadRequest, ErrInvalidPropfind
} }
return pf, 0, nil return pf, 0, nil
} }
@ -328,14 +328,14 @@ type MultiStatusWriter struct {
func (w *MultiStatusWriter) write(r *response) error { func (w *MultiStatusWriter) write(r *response) error {
switch len(r.Href) { switch len(r.Href) {
case 0: case 0:
return errInvalidResponse return ErrInvalidResponse
case 1: case 1:
if len(r.Propstat) > 0 != (r.Status == "") { if len(r.Propstat) > 0 != (r.Status == "") {
return errInvalidResponse return ErrInvalidResponse
} }
default: default:
if len(r.Propstat) > 0 || r.Status == "" { if len(r.Propstat) > 0 || r.Status == "" {
return errInvalidResponse return ErrInvalidResponse
} }
} }
err := w.writeHeader() err := w.writeHeader()
@ -506,12 +506,12 @@ func ReadProppatch(r io.Reader) (patches []Proppatch, status int, err error) {
case ixml.Name{Space: "DAV:", Local: "remove"}: case ixml.Name{Space: "DAV:", Local: "remove"}:
for _, p := range op.Prop { for _, p := range op.Prop {
if len(p.InnerXML) > 0 { if len(p.InnerXML) > 0 {
return nil, http.StatusBadRequest, errInvalidProppatch return nil, http.StatusBadRequest, ErrInvalidProppatch
} }
} }
remove = true remove = true
default: default:
return nil, http.StatusBadRequest, errInvalidProppatch return nil, http.StatusBadRequest, ErrInvalidProppatch
} }
patches = append(patches, Proppatch{Remove: remove, Props: op.Prop}) patches = append(patches, Proppatch{Remove: remove, Props: op.Prop})
} }

View File

@ -25,12 +25,12 @@ func TestReadLockInfo(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
input string input string
wantLI lockInfo wantLI LockInfo
wantStatus int wantStatus int
}{{ }{{
"bad: junk", "bad: junk",
"xxx", "xxx",
lockInfo{}, LockInfo{},
http.StatusBadRequest, http.StatusBadRequest,
}, { }, {
"bad: invalid owner XML", "bad: invalid owner XML",
@ -42,7 +42,7 @@ func TestReadLockInfo(t *testing.T) {
" <D:href> no end tag \n" + " <D:href> no end tag \n" +
" </D:owner>\n" + " </D:owner>\n" +
"</D:lockinfo>", "</D:lockinfo>",
lockInfo{}, LockInfo{},
http.StatusBadRequest, http.StatusBadRequest,
}, { }, {
"bad: invalid UTF-8", "bad: invalid UTF-8",
@ -54,7 +54,7 @@ func TestReadLockInfo(t *testing.T) {
" <D:href> \xff </D:href>\n" + " <D:href> \xff </D:href>\n" +
" </D:owner>\n" + " </D:owner>\n" +
"</D:lockinfo>", "</D:lockinfo>",
lockInfo{}, LockInfo{},
http.StatusBadRequest, http.StatusBadRequest,
}, { }, {
"bad: unfinished XML #1", "bad: unfinished XML #1",
@ -62,7 +62,7 @@ func TestReadLockInfo(t *testing.T) {
"<D:lockinfo xmlns:D='DAV:'>\n" + "<D:lockinfo xmlns:D='DAV:'>\n" +
" <D:lockscope><D:exclusive/></D:lockscope>\n" + " <D:lockscope><D:exclusive/></D:lockscope>\n" +
" <D:locktype><D:write/></D:locktype>\n", " <D:locktype><D:write/></D:locktype>\n",
lockInfo{}, LockInfo{},
http.StatusBadRequest, http.StatusBadRequest,
}, { }, {
"bad: unfinished XML #2", "bad: unfinished XML #2",
@ -71,12 +71,12 @@ func TestReadLockInfo(t *testing.T) {
" <D:lockscope><D:exclusive/></D:lockscope>\n" + " <D:lockscope><D:exclusive/></D:lockscope>\n" +
" <D:locktype><D:write/></D:locktype>\n" + " <D:locktype><D:write/></D:locktype>\n" +
" <D:owner>\n", " <D:owner>\n",
lockInfo{}, LockInfo{},
http.StatusBadRequest, http.StatusBadRequest,
}, { }, {
"good: empty", "good: empty",
"", "",
lockInfo{}, LockInfo{},
0, 0,
}, { }, {
"good: plain-text owner", "good: plain-text owner",
@ -86,7 +86,7 @@ func TestReadLockInfo(t *testing.T) {
" <D:locktype><D:write/></D:locktype>\n" + " <D:locktype><D:write/></D:locktype>\n" +
" <D:owner>gopher</D:owner>\n" + " <D:owner>gopher</D:owner>\n" +
"</D:lockinfo>", "</D:lockinfo>",
lockInfo{ LockInfo{
XMLName: ixml.Name{Space: "DAV:", Local: "lockinfo"}, XMLName: ixml.Name{Space: "DAV:", Local: "lockinfo"},
Exclusive: new(struct{}), Exclusive: new(struct{}),
Write: new(struct{}), Write: new(struct{}),
@ -105,7 +105,7 @@ func TestReadLockInfo(t *testing.T) {
" <D:href>http://example.org/~ejw/contact.html</D:href>\n" + " <D:href>http://example.org/~ejw/contact.html</D:href>\n" +
" </D:owner>\n" + " </D:owner>\n" +
"</D:lockinfo>", "</D:lockinfo>",
lockInfo{ LockInfo{
XMLName: ixml.Name{Space: "DAV:", Local: "lockinfo"}, XMLName: ixml.Name{Space: "DAV:", Local: "lockinfo"},
Exclusive: new(struct{}), Exclusive: new(struct{}),
Write: new(struct{}), Write: new(struct{}),
@ -117,7 +117,7 @@ func TestReadLockInfo(t *testing.T) {
}} }}
for _, tc := range testCases { for _, tc := range testCases {
li, status, err := readLockInfo(strings.NewReader(tc.input)) li, status, err := ReadLockInfo(strings.NewReader(tc.input))
if tc.wantStatus != 0 { if tc.wantStatus != 0 {
if err == nil { if err == nil {
t.Errorf("%s: got nil error, want non-nil", tc.desc) t.Errorf("%s: got nil error, want non-nil", tc.desc)
@ -128,7 +128,7 @@ func TestReadLockInfo(t *testing.T) {
continue continue
} }
if !reflect.DeepEqual(li, tc.wantLI) || status != tc.wantStatus { if !reflect.DeepEqual(li, tc.wantLI) || status != tc.wantStatus {
t.Errorf("%s:\ngot lockInfo=%v, status=%v\nwant lockInfo=%v, status=%v", t.Errorf("%s:\ngot LockInfo=%v, status=%v\nwant LockInfo=%v, status=%v",
tc.desc, li, status, tc.wantLI, tc.wantStatus) tc.desc, li, status, tc.wantLI, tc.wantStatus)
continue continue
} }
@ -503,7 +503,7 @@ func TestMultistatusWriter(t *testing.T) {
Status: "HTTP/1.1 200 OK", Status: "HTTP/1.1 200 OK",
}}, }},
}}, }},
wantErr: errInvalidResponse, wantErr: ErrInvalidResponse,
// default of http.responseWriter // default of http.responseWriter
wantCode: http.StatusOK, wantCode: http.StatusOK,
}, { }, {
@ -511,7 +511,7 @@ func TestMultistatusWriter(t *testing.T) {
responses: []response{{ responses: []response{{
Href: []string{"http://example.com/foo", "http://example.com/bar"}, Href: []string{"http://example.com/foo", "http://example.com/bar"},
}}, }},
wantErr: errInvalidResponse, wantErr: ErrInvalidResponse,
// default of http.responseWriter // default of http.responseWriter
wantCode: http.StatusOK, wantCode: http.StatusOK,
}, { }, {
@ -519,7 +519,7 @@ func TestMultistatusWriter(t *testing.T) {
responses: []response{{ responses: []response{{
Href: []string{"http://example.com/foo"}, Href: []string{"http://example.com/foo"},
}}, }},
wantErr: errInvalidResponse, wantErr: ErrInvalidResponse,
// default of http.responseWriter // default of http.responseWriter
wantCode: http.StatusOK, wantCode: http.StatusOK,
}, { }, {
@ -537,7 +537,7 @@ func TestMultistatusWriter(t *testing.T) {
}}, }},
Status: "HTTP/1.1 200 OK", Status: "HTTP/1.1 200 OK",
}}, }},
wantErr: errInvalidResponse, wantErr: ErrInvalidResponse,
// default of http.responseWriter // default of http.responseWriter
wantCode: http.StatusOK, wantCode: http.StatusOK,
}, { }, {
@ -557,7 +557,7 @@ func TestMultistatusWriter(t *testing.T) {
Status: "HTTP/1.1 200 OK", Status: "HTTP/1.1 200 OK",
}}, }},
}}, }},
wantErr: errInvalidResponse, wantErr: ErrInvalidResponse,
// default of http.responseWriter // default of http.responseWriter
wantCode: http.StatusOK, wantCode: http.StatusOK,
}} }}