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"
"regexp"
"strings"
"time"
)
/**
@ -28,6 +29,7 @@ type DavService struct {
BaseBean
matterDao *MatterDao
matterService *MatterService
lockSystem webdav.LockSystem
}
func (this *DavService) Init() {
@ -43,6 +45,8 @@ func (this *DavService) Init() {
this.matterService = b
}
// init the webdav lock system.
this.lockSystem = webdav.NewMemLS()
}
//get the depth in header. Not support infinity yet.
@ -536,9 +540,100 @@ func (this *DavService) HandleCopy(writer http.ResponseWriter, request *http.Req
}
//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

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/eyebluecn/tank/code/tool/i18n"
"net/http"
"strconv"
)
type WebResult struct {
@ -73,7 +74,13 @@ func FetchHttpStatus(code string) int {
} else if code == SERVER.Code {
return SERVER.HttpStatus
} else {
return UNKNOWN.HttpStatus
//if this is an int. regard it as statusCode
statusCode, err := strconv.Atoi(code)
if err != nil {
return UNKNOWN.HttpStatus
} else {
return statusCode
}
}
}
@ -104,6 +111,18 @@ func CustomWebResult(codeWrapper *CodeWrapper, description string) *WebResult {
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 {
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 nNode, ok := nDir.children[nFrag]; ok {
if nNode.children == nil {
return errNotADirectory
return ErrNotADirectory
}
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.
func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
if recursion == 1000 {
return http.StatusInternalServerError, errRecursionTooDeep
return http.StatusInternalServerError, ErrRecursionTooDeep
}
recursion++

View File

@ -11,39 +11,39 @@ import (
"strings"
)
// ifHeader is a disjunction (OR) of ifLists.
type ifHeader struct {
lists []ifList
// 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
// 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
// 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) {
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)
return ParseNoTagLists(s)
case AngleTokenType:
return ParseTaggedLists(s)
default:
return ifHeader{}, false
return IfHeader{}, false
}
}
func parseNoTagLists(s string) (h ifHeader, ok bool) {
func ParseNoTagLists(s string) (h IfHeader, ok bool) {
for {
l, remaining, ok := parseList(s)
l, remaining, ok := ParseList(s)
if !ok {
return ifHeader{}, false
return IfHeader{}, false
}
h.lists = append(h.lists, l)
h.Lists = append(h.Lists, l)
if remaining == "" {
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
for first := true; ; first = false {
tokenType, tokenStr, remaining := lex(s)
switch tokenType {
case angleTokenType:
case AngleTokenType:
if !first && n == 0 {
return ifHeader{}, false
return IfHeader{}, false
}
resourceTag, n = tokenStr, 0
s = remaining
case '(':
n++
l, remaining, ok := parseList(s)
l, remaining, ok := ParseList(s)
if !ok {
return ifHeader{}, false
return IfHeader{}, false
}
l.resourceTag = resourceTag
h.lists = append(h.lists, l)
l.ResourceTag = resourceTag
h.Lists = append(h.Lists, l)
if remaining == "" {
return h, true
}
s = remaining
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)
if tokenType != '(' {
return ifList{}, "", false
return IfList{}, "", false
}
for {
tokenType, _, remaining = lex(s)
if tokenType == ')' {
if len(l.conditions) == 0 {
return ifList{}, "", false
if len(l.Conditions) == 0 {
return IfList{}, "", false
}
return l, remaining, true
}
c, remaining, ok := parseCondition(s)
c, remaining, ok := ParseCondition(s)
if !ok {
return ifList{}, "", false
return IfList{}, "", false
}
l.conditions = append(l.conditions, c)
l.Conditions = append(l.Conditions, c)
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)
if tokenType == notTokenType {
if tokenType == NotTokenType {
c.Not = true
tokenType, tokenStr, s = lex(s)
}
switch tokenType {
case strTokenType, angleTokenType:
case StrTokenType, AngleTokenType:
c.Token = tokenStr
case squareTokenType:
case SquareTokenType:
c.ETag = tokenStr
default:
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.
// 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)
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) {
@ -138,7 +138,7 @@ func lex(s string) (tokenType rune, tokenStr string, remaining string) {
s = s[1:]
}
if len(s) == 0 {
return eofTokenType, "", ""
return EofTokenType, "", ""
}
i := 0
loop:
@ -152,22 +152,22 @@ loop:
if i != 0 {
tokenStr, remaining = s[:i], s[i:]
if tokenStr == "Not" {
return notTokenType, "", remaining
return NotTokenType, "", remaining
}
return strTokenType, tokenStr, remaining
return StrTokenType, tokenStr, remaining
}
j := 0
switch s[0] {
case '<':
j, tokenType = strings.IndexByte(s, '>'), angleTokenType
j, tokenType = strings.IndexByte(s, '>'), AngleTokenType
case '[':
j, tokenType = strings.IndexByte(s, ']'), squareTokenType
j, tokenType = strings.IndexByte(s, ']'), SquareTokenType
default:
return rune(s[0]), "", s[1:]
}
if j < 0 {
return errTokenType, "", ""
return ErrTokenType, "", ""
}
return tokenType, s[1:j], s[j+1:]
}

View File

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

View File

@ -37,7 +37,7 @@ type Condition struct {
// of host operating system convention.
type LockSystem interface {
// 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
// can be named. Empty names are ignored.
//
@ -113,14 +113,14 @@ type LockDetails struct {
// NewMemLS returns a new in-memory LockSystem.
func NewMemLS() LockSystem {
return &memLS{
return &MemLS{
byName: make(map[string]*memLSNode),
byToken: make(map[string]*memLSNode),
gen: uint64(time.Now().Unix()),
}
}
type memLS struct {
type MemLS struct {
mu sync.Mutex
byName map[string]*memLSNode
byToken map[string]*memLSNode
@ -130,12 +130,12 @@ type memLS struct {
byExpiry byExpiry
}
func (m *memLS) nextToken() string {
func (m *MemLS) nextToken() string {
m.gen++
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 {
if now.Before(m.byExpiry[0].expiry) {
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()
defer m.mu.Unlock()
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
// 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.
//
// 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.
for _, c := range conditions {
n = m.byToken[c.Token]
@ -209,9 +209,9 @@ func (m *memLS) lookup(name string, conditions ...Condition) (n *memLSNode) {
return nil
}
func (m *memLS) hold(n *memLSNode) {
func (m *MemLS) hold(n *memLSNode) {
if n.held {
panic("webdav: memLS inconsistent held state")
panic("webdav: MemLS inconsistent held state")
}
n.held = true
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 {
panic("webdav: memLS inconsistent held state")
panic("webdav: MemLS inconsistent held state")
}
n.held = false
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()
defer m.mu.Unlock()
m.collectExpiredNodes(now)
@ -249,7 +249,7 @@ func (m *memLS) Create(now time.Time, details LockDetails) (string, error) {
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()
defer m.mu.Unlock()
m.collectExpiredNodes(now)
@ -272,7 +272,7 @@ func (m *memLS) Refresh(now time.Time, token string, duration time.Duration) (Lo
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()
defer m.mu.Unlock()
m.collectExpiredNodes(now)
@ -288,7 +288,7 @@ func (m *memLS) Unlock(now time.Time, token string) error {
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 {
n := m.byName[name0]
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 {
n := m.byName[name0]
if n == nil {
@ -333,7 +333,7 @@ func (m *memLS) create(name string) (ret *memLSNode) {
return ret
}
func (m *memLS) remove(n *memLSNode) {
func (m *MemLS) remove(n *memLSNode) {
delete(m.byToken, n.token)
n.token = ""
walkToRoot(n.details.Root, func(name0 string, first bool) bool {
@ -376,7 +376,7 @@ type memLSNode struct {
refCount int
// expiry is when this node's lock expires.
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.
byExpiryIndex int
// 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
// 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.
func parseTimeout(s string) (time.Duration, error) {
func ParseTimeout(s string) (time.Duration, error) {
if s == "" {
return infiniteTimeout, nil
}
@ -431,15 +431,15 @@ func parseTimeout(s string) (time.Duration, error) {
}
const pre = "Second-"
if !strings.HasPrefix(s, pre) {
return 0, errInvalidTimeout
return 0, ErrInvalidTimeout
}
s = s[len(pre):]
if s == "" || s[0] < '0' || '9' < s[0] {
return 0, errInvalidTimeout
return 0, ErrInvalidTimeout
}
n, err := strconv.ParseInt(s, 10, 64)
if err != nil || 1<<32-1 < n {
return 0, errInvalidTimeout
return 0, ErrInvalidTimeout
}
return time.Duration(n) * time.Second, nil
}

View File

@ -97,7 +97,7 @@ func lockTestZeroDepth(name string) bool {
func TestMemLSCanCreate(t *testing.T) {
now := time.Unix(0, 0)
m := NewMemLS().(*memLS)
m := NewMemLS().(*MemLS)
for _, name := range lockTestNames {
_, err := m.Create(now, LockDetails{
@ -157,7 +157,7 @@ func TestMemLSCanCreate(t *testing.T) {
func TestMemLSLookup(t *testing.T) {
now := time.Unix(0, 0)
m := NewMemLS().(*memLS)
m := NewMemLS().(*MemLS)
badToken := m.nextToken()
t.Logf("badToken=%q", badToken)
@ -206,7 +206,7 @@ func TestMemLSLookup(t *testing.T) {
func TestMemLSConfirm(t *testing.T) {
now := time.Unix(0, 0)
m := NewMemLS().(*memLS)
m := NewMemLS().(*MemLS)
alice, err := m.Create(now, LockDetails{
Root: "/alice",
Duration: infiniteTimeout,
@ -302,7 +302,7 @@ func TestMemLSConfirm(t *testing.T) {
func TestMemLSNonCanonicalRoot(t *testing.T) {
now := time.Unix(0, 0)
m := NewMemLS().(*memLS)
m := NewMemLS().(*MemLS)
token, err := m.Create(now, LockDetails{
Root: "/foo/./bar//",
Duration: 1 * time.Second,
@ -322,7 +322,7 @@ func TestMemLSNonCanonicalRoot(t *testing.T) {
}
func TestMemLSExpiry(t *testing.T) {
m := NewMemLS().(*memLS)
m := NewMemLS().(*MemLS)
testCases := []string{
"setNow 0",
"create /a.5",
@ -451,7 +451,7 @@ func TestMemLSExpiry(t *testing.T) {
func TestMemLS(t *testing.T) {
now := time.Unix(0, 0)
m := NewMemLS().(*memLS)
m := NewMemLS().(*MemLS)
rng := rand.New(rand.NewSource(0))
tokens := map[string]string{}
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()
defer m.mu.Unlock()
@ -653,11 +653,11 @@ func TestParseTimeout(t *testing.T) {
}, {
"Infinitesimal",
0,
errInvalidTimeout,
ErrInvalidTimeout,
}, {
"infinite",
0,
errInvalidTimeout,
ErrInvalidTimeout,
}, {
"Second-0",
0 * time.Second,
@ -677,31 +677,31 @@ func TestParseTimeout(t *testing.T) {
}, {
"junk",
0,
errInvalidTimeout,
ErrInvalidTimeout,
}, {
"Second-",
0,
errInvalidTimeout,
ErrInvalidTimeout,
}, {
"Second--1",
0,
errInvalidTimeout,
ErrInvalidTimeout,
}, {
"Second--123",
0,
errInvalidTimeout,
ErrInvalidTimeout,
}, {
"Second-+123",
0,
errInvalidTimeout,
ErrInvalidTimeout,
}, {
"Second-0x123",
0,
errInvalidTimeout,
ErrInvalidTimeout,
}, {
"second-123",
0,
errInvalidTimeout,
ErrInvalidTimeout,
}, {
"Second-4294967295",
4294967295 * time.Second,
@ -711,7 +711,7 @@ func TestParseTimeout(t *testing.T) {
// must not be greater than 2^32-1."
"Second-4294967296",
0,
errInvalidTimeout,
ErrInvalidTimeout,
}, {
// 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 {
got, gotErr := parseTimeout(tc.s)
got, gotErr := ParseTimeout(tc.s)
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)
}

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) {
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) {
status, err := http.StatusBadRequest, errUnsupportedMethod
status, err := http.StatusBadRequest, ErrUnsupportedMethod
if h.FileSystem == nil {
status, err = http.StatusInternalServerError, errNoFileSystem
status, err = http.StatusInternalServerError, ErrNoFileSystem
} else if h.LockSystem == nil {
status, err = http.StatusInternalServerError, errNoLockSystem
status, err = http.StatusInternalServerError, ErrNoLockSystem
} else {
switch r.Method {
case "OPTIONS":
@ -131,13 +131,13 @@ func (h *Handler) confirmLocks(r *http.Request, src, dst string) (release func()
}, 0, nil
}
ih, ok := parseIfHeader(hdr)
ih, ok := ParseIfHeader(hdr)
if !ok {
return nil, http.StatusBadRequest, errInvalidIfHeader
return nil, http.StatusBadRequest, ErrInvalidIfHeader
}
// ih is a disjunction (OR) of ifLists, so any ifList will do.
for _, l := range ih.lists {
lsrc := l.resourceTag
// ih is a disjunction (OR) of ifLists, so any IfList will do.
for _, l := range ih.Lists {
lsrc := l.ResourceTag
if lsrc == "" {
lsrc = src
} else {
@ -153,7 +153,7 @@ func (h *Handler) confirmLocks(r *http.Request, src, dst string) (release func()
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 {
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) {
hdr := r.Header.Get("Destination")
if hdr == "" {
return http.StatusBadRequest, errInvalidDestination
return http.StatusBadRequest, ErrInvalidDestination
}
u, err := url.Parse(hdr)
if err != nil {
return http.StatusBadRequest, errInvalidDestination
return http.StatusBadRequest, ErrInvalidDestination
}
if u.Host != "" && u.Host != r.Host {
return http.StatusBadGateway, errInvalidDestination
return http.StatusBadGateway, ErrInvalidDestination
}
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 == "" {
return http.StatusBadGateway, errInvalidDestination
return http.StatusBadGateway, ErrInvalidDestination
}
if dst == src {
return http.StatusForbidden, errDestinationEqualsSource
return http.StatusForbidden, ErrDestinationEqualsSource
}
ctx := r.Context()
@ -366,7 +366,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
if depth != 0 && depth != InfiniteDepth {
// 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"."
return http.StatusBadRequest, errInvalidDepth
return http.StatusBadRequest, ErrInvalidDepth
}
}
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"."
if hdr := r.Header.Get("Depth"); hdr != "" {
if ParseDepth(hdr) != InfiniteDepth {
return http.StatusBadRequest, errInvalidDepth
return http.StatusBadRequest, ErrInvalidDepth
}
}
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) {
duration, err := parseTimeout(r.Header.Get("Timeout"))
duration, err := ParseTimeout(r.Header.Get("Timeout"))
if err != nil {
return http.StatusBadRequest, err
}
li, status, err := readLockInfo(r.Body)
li, status, err := ReadLockInfo(r.Body)
if err != nil {
return status, err
}
ctx := r.Context()
token, ld, now, created := "", LockDetails{}, time.Now(), false
if li == (lockInfo{}) {
// An empty lockInfo means to refresh the lock.
ih, ok := parseIfHeader(r.Header.Get("If"))
if li == (LockInfo{}) {
// An empty LockInfo means to refresh the lock.
ih, ok := ParseIfHeader(r.Header.Get("If"))
if !ok {
return http.StatusBadRequest, errInvalidIfHeader
return http.StatusBadRequest, ErrInvalidIfHeader
}
if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 {
token = ih.lists[0].conditions[0].Token
if len(ih.Lists) == 1 && len(ih.Lists[0].Conditions) == 1 {
token = ih.Lists[0].Conditions[0].Token
}
if token == "" {
return http.StatusBadRequest, errInvalidLockToken
return http.StatusBadRequest, ErrInvalidLockToken
}
ld, err = h.LockSystem.Refresh(now, token, duration)
if err != nil {
@ -430,7 +430,7 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus
if depth != 0 && depth != 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".
return http.StatusBadRequest, errInvalidDepth
return http.StatusBadRequest, ErrInvalidDepth
}
}
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".
w.WriteHeader(http.StatusCreated)
}
writeLockInfo(w, token, ld)
_, _ = WriteLockInfo(w, token, ld)
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.
t := r.Header.Get("Lock-Token")
if len(t) < 2 || t[0] != '<' || t[len(t)-1] != '>' {
return http.StatusBadRequest, errInvalidLockToken
return http.StatusBadRequest, ErrInvalidLockToken
}
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 != "" {
depth = ParseDepth(hdr)
if depth == InvalidDepth {
return http.StatusBadRequest, errInvalidDepth
return http.StatusBadRequest, ErrInvalidDepth
}
}
pf, status, err := ReadPropfind(r.Body)
@ -685,22 +685,22 @@ func StatusText(code int) string {
}
var (
errDestinationEqualsSource = errors.New("webdav: destination equals source")
errDirectoryNotEmpty = errors.New("webdav: directory not empty")
errInvalidDepth = errors.New("webdav: invalid depth")
errInvalidDestination = errors.New("webdav: invalid destination")
errInvalidIfHeader = errors.New("webdav: invalid If header")
errInvalidLockInfo = errors.New("webdav: invalid lock info")
errInvalidLockToken = errors.New("webdav: invalid lock token")
errInvalidPropfind = errors.New("webdav: invalid propfind")
errInvalidProppatch = errors.New("webdav: invalid proppatch")
errInvalidResponse = errors.New("webdav: invalid response")
errInvalidTimeout = errors.New("webdav: invalid timeout")
errNoFileSystem = errors.New("webdav: no file system")
errNoLockSystem = errors.New("webdav: no lock system")
errNotADirectory = errors.New("webdav: not a directory")
errPrefixMismatch = errors.New("webdav: prefix mismatch")
errRecursionTooDeep = errors.New("webdav: recursion too deep")
errUnsupportedLockInfo = errors.New("webdav: unsupported lock info")
errUnsupportedMethod = errors.New("webdav: unsupported method")
ErrDestinationEqualsSource = errors.New("webdav: destination equals source")
ErrDirectoryNotEmpty = errors.New("webdav: directory not empty")
ErrInvalidDepth = errors.New("webdav: invalid depth")
ErrInvalidDestination = errors.New("webdav: invalid destination")
ErrInvalidIfHeader = errors.New("webdav: invalid If header")
ErrInvalidLockInfo = errors.New("webdav: invalid lock info")
ErrInvalidLockToken = errors.New("webdav: invalid lock token")
ErrInvalidPropfind = errors.New("webdav: invalid propfind")
ErrInvalidProppatch = errors.New("webdav: invalid proppatch")
ErrInvalidResponse = errors.New("webdav: invalid response")
ErrInvalidTimeout = errors.New("webdav: invalid timeout")
ErrNoFileSystem = errors.New("webdav: no file system")
ErrNoLockSystem = errors.New("webdav: no lock system")
ErrNotADirectory = errors.New("webdav: not a directory")
ErrPrefixMismatch = errors.New("webdav: prefix mismatch")
ErrRecursionTooDeep = errors.New("webdav: recursion too deep")
ErrUnsupportedLockInfo = errors.New("webdav: unsupported lock info")
ErrUnsupportedMethod = errors.New("webdav: unsupported method")
)

View File

@ -36,7 +36,7 @@ import (
)
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockinfo
type lockInfo struct {
type LockInfo struct {
XMLName ixml.Name `xml:"lockinfo"`
Exclusive *struct{} `xml:"lockscope>exclusive"`
Shared *struct{} `xml:"lockscope>shared"`
@ -49,23 +49,23 @@ type owner struct {
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}
if err = ixml.NewDecoder(c).Decode(&li); err != nil {
if err == io.EOF {
if c.n == 0 {
// An empty body means to refresh the lock.
// 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
// the only types of locks that seem to matter.
if li.Exclusive == nil || li.Shared != nil || li.Write == nil {
return lockInfo{}, http.StatusNotImplemented, errUnsupportedLockInfo
return LockInfo{}, http.StatusNotImplemented, ErrUnsupportedLockInfo
}
return li, 0, nil
}
@ -81,7 +81,7 @@ func (c *countingReader) Read(p []byte) (int, error) {
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"
if ld.ZeroDepth {
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
return Propfind{Allprop: new(struct{})}, 0, nil
}
err = errInvalidPropfind
err = ErrInvalidPropfind
}
return Propfind{}, http.StatusBadRequest, err
}
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) {
return Propfind{}, http.StatusBadRequest, errInvalidPropfind
return Propfind{}, http.StatusBadRequest, ErrInvalidPropfind
}
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 {
return Propfind{}, http.StatusBadRequest, errInvalidPropfind
return Propfind{}, http.StatusBadRequest, ErrInvalidPropfind
}
return pf, 0, nil
}
@ -328,14 +328,14 @@ type MultiStatusWriter struct {
func (w *MultiStatusWriter) write(r *response) error {
switch len(r.Href) {
case 0:
return errInvalidResponse
return ErrInvalidResponse
case 1:
if len(r.Propstat) > 0 != (r.Status == "") {
return errInvalidResponse
return ErrInvalidResponse
}
default:
if len(r.Propstat) > 0 || r.Status == "" {
return errInvalidResponse
return ErrInvalidResponse
}
}
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"}:
for _, p := range op.Prop {
if len(p.InnerXML) > 0 {
return nil, http.StatusBadRequest, errInvalidProppatch
return nil, http.StatusBadRequest, ErrInvalidProppatch
}
}
remove = true
default:
return nil, http.StatusBadRequest, errInvalidProppatch
return nil, http.StatusBadRequest, ErrInvalidProppatch
}
patches = append(patches, Proppatch{Remove: remove, Props: op.Prop})
}

View File

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