-
Notifications
You must be signed in to change notification settings - Fork 22
/
webdav.go
137 lines (116 loc) · 3.99 KB
/
webdav.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright 2015 Matthew Holt
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package webdav implements a WebDAV server handler module for Caddy.
//
// Derived from work by Henrique Dias: https://github.com/hacdias/caddy-webdav
package webdav
import (
"context"
"net/http"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"go.uber.org/zap"
"golang.org/x/net/webdav"
)
func init() {
caddy.RegisterModule(WebDAV{})
}
// WebDAV implements an HTTP handler for responding to WebDAV clients.
type WebDAV struct {
// The root directory out of which to serve files. If
// not specified, `{http.vars.root}` will be used if
// set; otherwise, the current directory is assumed.
// Accepts placeholders.
Root string `json:"root,omitempty"`
// The base path prefix used to access the WebDAV share.
// Should be used if one more more matchers are used with the
// webdav directive and it's needed to let the webdav share know
// what the request base path will be.
// For example:
// webdav /some/path/match/* {
// root /path
// prefix /some/path/match
// }
// Accepts placeholders.
Prefix string `json:"prefix,omitempty"`
lockSystem webdav.LockSystem
logger *zap.Logger
}
// CaddyModule returns the Caddy module information.
func (WebDAV) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.handlers.webdav",
New: func() caddy.Module { return new(WebDAV) },
}
}
// Provision sets up the module.
func (wd *WebDAV) Provision(ctx caddy.Context) error {
wd.logger = ctx.Logger(wd)
wd.lockSystem = webdav.NewMemLS()
if wd.Root == "" {
wd.Root = "{http.vars.root}"
}
return nil
}
func (wd WebDAV) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
// TODO: integrate with caddy 2's existing auth features to enforce read-only?
// read methods: GET, HEAD, OPTIONS
// write methods: POST, PUT, PATCH, DELETE, COPY, MKCOL, MOVE, PROPPATCH
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
root := repl.ReplaceAll(wd.Root, ".")
prefix := repl.ReplaceAll(wd.Prefix, "")
wdHandler := webdav.Handler{
Prefix: prefix,
FileSystem: webdav.Dir(root),
LockSystem: wd.lockSystem,
Logger: func(req *http.Request, err error) {
if err != nil {
wd.logger.Error("internal handler error",
zap.Error(err),
zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: req}),
)
}
},
}
// Excerpt from RFC4918, section 9.4:
//
// GET, when applied to a collection, may return the contents of an
// "index.html" resource, a human-readable view of the contents of
// the collection, or something else altogether.
//
// GET and HEAD, when applied to a collection, will behave the same as PROPFIND method.
if r.Method == http.MethodGet || r.Method == http.MethodHead {
info, err := wdHandler.FileSystem.Stat(context.TODO(), r.URL.Path)
if err == nil && info.IsDir() {
r.Method = "PROPFIND"
if r.Header.Get("Depth") == "" {
r.Header.Add("Depth", "1")
}
}
}
if r.Method == http.MethodHead {
w = emptyBodyResponseWriter{w}
}
wdHandler.ServeHTTP(w, r)
return nil
}
// emptyBodyResponseWriter is a response writer that does not write a body.
type emptyBodyResponseWriter struct{ http.ResponseWriter }
func (w emptyBodyResponseWriter) Write(data []byte) (int, error) { return 0, nil }
// Interface guards
var (
_ caddyhttp.MiddlewareHandler = (*WebDAV)(nil)
_ caddyfile.Unmarshaler = (*WebDAV)(nil)
)