-
Notifications
You must be signed in to change notification settings - Fork 583
add webdav #217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
add webdav #217
Changes from all commits
ca9e36e
2e9c93e
79cd8e6
e76e905
19247e4
1585f5b
29891fc
869a05f
3427b3f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,32 @@ | ||
| module github.com/codeskyblue/gohttpserver | ||
|
|
||
| go 1.16 | ||
| go 1.23.0 | ||
|
|
||
| require ( | ||
| github.com/alecthomas/kingpin v2.2.6+incompatible | ||
| github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect | ||
| github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect | ||
| github.com/codeskyblue/dockerignore v0.0.0-20151214070507-de82dee623d9 | ||
| github.com/codeskyblue/go-accesslog v0.0.0-20171215023101-6188d3bd9371 | ||
| github.com/codeskyblue/openid-go v0.0.0-20160923065855-0d30842b2fb4 | ||
| github.com/fork2fix/go-plist v0.0.0-20181126021357-36960be5e636 | ||
| github.com/go-yaml/yaml v2.1.0+incompatible | ||
| github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d | ||
| github.com/gorilla/context v1.1.2 // indirect | ||
| github.com/gorilla/handlers v1.4.0 | ||
| github.com/gorilla/mux v1.6.2 | ||
| github.com/gorilla/sessions v1.2.0 | ||
| github.com/pkg/errors v0.8.0 // indirect | ||
| github.com/shogo82148/androidbinary v0.0.0-20180627093851-01c4bfa8b3b5 | ||
| github.com/smartystreets/goconvey v1.6.4 // indirect | ||
| github.com/stretchr/testify v1.3.0 | ||
| golang.org/x/net v0.38.0 // indirect | ||
| golang.org/x/net v0.38.0 | ||
| golang.org/x/text v0.23.0 | ||
| ) | ||
|
|
||
| require ( | ||
| github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect | ||
| github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect | ||
| github.com/davecgh/go-spew v1.1.0 // indirect | ||
| github.com/gorilla/context v1.1.2 // indirect | ||
| github.com/gorilla/securecookie v1.1.1 // indirect | ||
| github.com/pkg/errors v0.8.0 // indirect | ||
| github.com/pmezard/go-difflib v1.0.0 // indirect | ||
| github.com/smartystreets/goconvey v1.6.4 // indirect | ||
| howett.net/plist v0.0.0-20201203080718-1454fab16a06 // indirect | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,7 @@ import ( | |
| "github.com/go-yaml/yaml" | ||
| "github.com/gorilla/mux" | ||
| "github.com/shogo82148/androidbinary/apk" | ||
| "golang.org/x/net/webdav" | ||
| ) | ||
|
|
||
| const YAMLCONF = ".ghs.yml" | ||
|
|
@@ -62,6 +63,7 @@ type HTTPStaticServer struct { | |
|
|
||
| indexes []IndexFileItem | ||
| m *mux.Router | ||
| Handler *webdav.Handler | ||
| bufPool sync.Pool // use sync.Pool caching buf to reduce gc ratio | ||
| } | ||
|
|
||
|
|
@@ -75,11 +77,16 @@ func NewHTTPStaticServer(root string, noIndex bool) *HTTPStaticServer { | |
| root = root + "/" | ||
| } | ||
| log.Printf("root path: %s\n", root) | ||
| fs := &webdav.Handler{ | ||
| FileSystem: webdav.Dir(root), | ||
| LockSystem: webdav.NewMemLS(), | ||
| } | ||
| m := mux.NewRouter() | ||
| s := &HTTPStaticServer{ | ||
| Root: root, | ||
| Theme: "black", | ||
| m: m, | ||
| Handler: fs, | ||
| bufPool: sync.Pool{ | ||
| New: func() interface{} { return make([]byte, 32*1024) }, | ||
| }, | ||
|
|
@@ -108,13 +115,31 @@ func NewHTTPStaticServer(root string, noIndex bool) *HTTPStaticServer { | |
| m.HandleFunc("/{path:.*}", s.hIndex).Methods("GET", "HEAD") | ||
| m.HandleFunc("/{path:.*}", s.hUploadOrMkdir).Methods("POST") | ||
| m.HandleFunc("/{path:.*}", s.hDelete).Methods("DELETE") | ||
| m.HandleFunc("/{path:.*}", s.hWebdav).Methods("OPTIONS", "PROPFIND", "PUT", "LOCK", "UNLOCK", "MKCOL", "MOVE", "PROPPATCH") | ||
| return s | ||
| } | ||
|
|
||
| func (s *HTTPStaticServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
| s.m.ServeHTTP(w, r) | ||
| } | ||
|
|
||
| func (s *HTTPStaticServer) hWebdav(w http.ResponseWriter, r *http.Request) { | ||
| path := mux.Vars(r)["path"] | ||
| realPath := s.getRealPath(r) | ||
| auth := s.readAccessConf(realPath) | ||
|
|
||
| if filepath.Base(path) == YAMLCONF { | ||
| http.Error(w, "Security warning, not allowed to rw", http.StatusForbidden) | ||
| return | ||
| } | ||
| if !auth.canWebdavAccess(w, r) { | ||
| http.Error(w, "Not authorized", http.StatusUnauthorized) | ||
| return | ||
| } | ||
|
|
||
| s.Handler.ServeHTTP(w, r) | ||
| } | ||
|
|
||
| // Return real path with Seperator(/) | ||
| func (s *HTTPStaticServer) getRealPath(r *http.Request) string { | ||
| path := mux.Vars(r)["path"] | ||
|
|
@@ -133,6 +158,18 @@ func (s *HTTPStaticServer) getRealPath(r *http.Request) string { | |
| func (s *HTTPStaticServer) hIndex(w http.ResponseWriter, r *http.Request) { | ||
| path := mux.Vars(r)["path"] | ||
| realPath := s.getRealPath(r) | ||
| auth := s.readAccessConf(realPath) | ||
|
|
||
| // Only apply WebDAV-specific access control for non-GET/HEAD methods. | ||
| // For standard read operations (GET/HEAD), rely on the normal authentication | ||
| // mechanism (e.g., session-based auth/middleware) instead of canWebdavAccess. | ||
| if r.Method != http.MethodGet && r.Method != http.MethodHead { | ||
| if !auth.canWebdavAccess(w, r) { | ||
| http.Error(w, "Get forbidden", http.StatusForbidden) | ||
| return | ||
| } | ||
| } | ||
|
|
||
| if r.FormValue("json") == "true" { | ||
| s.hJSONList(w, r) | ||
| return | ||
|
|
@@ -165,7 +202,7 @@ func (s *HTTPStaticServer) hIndex(w http.ResponseWriter, r *http.Request) { | |
| if r.FormValue("download") == "true" { | ||
| w.Header().Set("Content-Disposition", "attachment; filename="+strconv.Quote(filepath.Base(path))) | ||
| } | ||
| http.ServeFile(w, r, realPath) | ||
| s.Handler.ServeHTTP(w, r) | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -175,10 +212,13 @@ func (s *HTTPStaticServer) hDelete(w http.ResponseWriter, req *http.Request) { | |
| // path = filepath.Clean(path) // for safe reason, prevent path contain .. | ||
| auth := s.readAccessConf(realPath) | ||
| if !auth.canDelete(req) { | ||
| http.Error(w, "Delete forbidden", http.StatusForbidden) | ||
| return | ||
| if !auth.canWebdavAccess(w, req) { | ||
| http.Error(w, "Delete forbidden", http.StatusForbidden) | ||
| return | ||
| } | ||
| } | ||
|
|
||
| s.Handler.ServeHTTP(w, req) | ||
| return | ||
| // TODO: path safe check | ||
| err := os.RemoveAll(realPath) | ||
| if err != nil { | ||
|
|
@@ -502,6 +542,21 @@ type AccessConf struct { | |
|
|
||
| var reCache = make(map[string]*regexp.Regexp) | ||
|
|
||
| func (c *AccessConf) canWebdavAccess(w http.ResponseWriter, r *http.Request) bool { | ||
| if len(c.Users) == 0 { | ||
| return true | ||
| } | ||
| w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) | ||
|
Comment on lines
+545
to
+549
|
||
|
|
||
| username, password, _ := r.BasicAuth() | ||
| for _, rule := range c.Users { | ||
| if rule.Email == username && rule.Token == password { | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| func (c *AccessConf) canAccess(fileName string) bool { | ||
| for _, table := range c.AccessTables { | ||
| pattern, ok := reCache[table.Regex] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The authorization logic is confusing and potentially incorrect. The code checks if delete is NOT allowed, and if so, it then checks WebDAV access. This creates a fallback where if session-based delete is forbidden, WebDAV access with basic auth can still proceed. This may unintentionally allow delete operations through WebDAV when they should be forbidden. The logic should be clearer about when each authentication method applies and whether they should be alternatives or both required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot open a new pull request to apply changes based on this feedback