Skip to content

Commit

Permalink
refactor: config check
Browse files Browse the repository at this point in the history
Signed-off-by: 117503445 <[email protected]>
  • Loading branch information
117503445 committed Feb 17, 2024
1 parent 7c8ac95 commit 0547fcc
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 88 deletions.
19 changes: 19 additions & 0 deletions internal/common/path.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package common

import (
"os"
)

// GetProjectRoot returns the root directory of the project, e.g. "/root/project/GoWebDAV/"
func GetProjectRoot() string {
wd, _ := os.Getwd()
// look for parents until we find a directory with a .git folder
for {
if _, err := os.Stat(wd + "/.git"); err == nil {
break
}
wd = wd[:len(wd)-1]
}

return wd
}
2 changes: 0 additions & 2 deletions internal/server/config.go

This file was deleted.

125 changes: 125 additions & 0 deletions internal/server/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package server

import (
"errors"
"net/http"
"os"
"strings"

"golang.org/x/net/webdav"
)

type HandlerConfig struct {
Prefix string
PathDir string
Username string
Password string
ReadOnly bool
}

type handler struct {
handler *webdav.Handler

prefix string // URL prefix
dirPath string // File system directory

username string // HTTP Basic Auth Username. if empty, no auth
password string // HTTP Basic Auth Password

readOnly bool // if true, only allow GET, OPTIONS, PROPFIND, HEAD
}

func NewHandler(cfg *HandlerConfig) *handler {
return &handler{
handler: &webdav.Handler{
FileSystem: webdav.Dir(cfg.PathDir),
LockSystem: webdav.NewMemLS(),
Prefix: cfg.Prefix,
},
prefix: cfg.Prefix,
dirPath: cfg.PathDir,
username: cfg.Username,
password: cfg.Password,
readOnly: cfg.ReadOnly,
}
}

func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
enableBasicAuth := h.username != ""
if enableBasicAuth {
username, password, ok := req.BasicAuth()
// log.Debug().Str("username", username).Str("password", password).Bool("ok", ok).Msg("BasicAuth Request")
if !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
w.WriteHeader(http.StatusUnauthorized)
return
}

if username != h.username || password != h.password {
w.WriteHeader(http.StatusUnauthorized)
return
}
}

if h.readOnly {
allowedMethods := map[string]bool{
"GET": true,
"OPTIONS": true,
"PROPFIND": true,
"HEAD": true,
}
if !allowedMethods[req.Method] {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
}

h.handler.ServeHTTP(w, req)
}

func checkHandlerConfig(cfg *HandlerConfig) error {
// prefix must start with "/", contains only one "/"
if cfg.Prefix == "" || cfg.Prefix[0] != '/' {
return errors.New("prefix must start with /")
}

// prefix must contain only one "/"
if strings.Count(cfg.Prefix, "/") != 1 {
return errors.New("prefix must contain only one /")
}

// prefix must not contain not allowed characters
notAllowedChars := []string{"?", "%", "#", "&"}
for _, char := range notAllowedChars {
if strings.Contains(cfg.Prefix, char) {
return errors.New("prefix must not contain " + char)
}
}

// pathDir must be a valid directory
if fileinfo, err := os.Stat(cfg.PathDir); err != nil {
return err
} else if !fileinfo.IsDir() {
return errors.New("pathDir must be a directory")
}

return nil
}

func checkHandlerConfigs(cfgs []*HandlerConfig) error {
for _, cfg := range cfgs {
if err := checkHandlerConfig(cfg); err != nil {
return err
}
}

prefixs := make(map[string]bool)
for _, cfg := range cfgs {
if _, ok := prefixs[cfg.Prefix]; ok {
return errors.New("prefix " + cfg.Prefix + " is duplicated")
}
prefixs[cfg.Prefix] = true
}

return nil
}
112 changes: 112 additions & 0 deletions internal/server/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package server

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestHandlerConfig(t *testing.T) {
assert := assert.New(t)

wd, err := os.Getwd()
assert.Nil(err)

cases := []struct {
cfg *HandlerConfig
valid bool
}{
{
&HandlerConfig{
Prefix: "/data",
PathDir: wd,
},
true,
}, {
&HandlerConfig{
Prefix: "/",
PathDir: wd,
},
true,
}, {
&HandlerConfig{
Prefix: "data",
PathDir: wd,
},
false,
}, {
&HandlerConfig{
Prefix: "/data/",
PathDir: wd,
},
false,
}, {
&HandlerConfig{
Prefix: "/114?514",
PathDir: wd,
},
false,
}, {
&HandlerConfig{
Prefix: "/data",
PathDir: "/114514",
},
false,
},
}

for _, c := range cases {
if c.valid {
assert.Nil(checkHandlerConfig(c.cfg), "cfg: %+v should be valid", c.cfg)
} else {
assert.NotNil(checkHandlerConfig(c.cfg), "cfg: %+v should be invalid", c.cfg)
}
}
}

func TestHandlerConfigs(t *testing.T) {
assert := assert.New(t)

wd, err := os.Getwd()
assert.Nil(err)

cases := []struct {
cfgs []*HandlerConfig
valid bool
}{
{
[]*HandlerConfig{
{
Prefix: "/data1",
PathDir: wd,
},
{
Prefix: "/data2",
PathDir: wd,
},
},
true,
}, {
[]*HandlerConfig{
{
Prefix: "/data1",
PathDir: wd,
},
{
Prefix: "/data1",
PathDir: wd,
},
},
false,
},
}

for _, c := range cases {
if c.valid {
assert.Nil(checkHandlerConfigs(c.cfgs), "cfgs: %+v should be valid", c.cfgs)
} else {
assert.NotNil(checkHandlerConfigs(c.cfgs), "cfgs: %+v should be invalid", c.cfgs)
}
}
}
95 changes: 9 additions & 86 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,106 +10,23 @@ import (
"golang.org/x/net/webdav"
)

type HandlerConfig struct {
Prefix string
PathDir string
Username string
Password string
ReadOnly bool
}

type Handler struct {
handler *webdav.Handler

prefix string // URL prefix
dirPath string // File system directory

username string // HTTP Basic Auth Username. if empty, no auth
password string // HTTP Basic Auth Password

readOnly bool // if true, only allow GET, OPTIONS, PROPFIND, HEAD
}

// func NewHandler(prefix, dirPath, username, password string, readOnly bool) *Handler {
// return &Handler{
// handler: &webdav.Handler{
// FileSystem: webdav.Dir(dirPath),
// LockSystem: webdav.NewMemLS(),
// Prefix: prefix,
// },
// }
// }

func NewHandler(cfg *HandlerConfig) *Handler {
return &Handler{
handler: &webdav.Handler{
FileSystem: webdav.Dir(cfg.PathDir),
LockSystem: webdav.NewMemLS(),
Prefix: cfg.Prefix,
},
prefix: cfg.Prefix,
dirPath: cfg.PathDir,
username: cfg.Username,
password: cfg.Password,
readOnly: cfg.ReadOnly,
}
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
enableBasicAuth := h.username != ""
if enableBasicAuth {
username, password, ok := req.BasicAuth()
// log.Debug().Str("username", username).Str("password", password).Bool("ok", ok).Msg("BasicAuth Request")
if !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
w.WriteHeader(http.StatusUnauthorized)
return
}

if username != h.username || password != h.password {
w.WriteHeader(http.StatusUnauthorized)
return
}
}

if h.readOnly {
allowedMethods := map[string]bool{
"GET": true,
"OPTIONS": true,
"PROPFIND": true,
"HEAD": true,
}
if !allowedMethods[req.Method] {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
}

h.handler.ServeHTTP(w, req)
}

type WebDAVServer struct {
addr string // Address to listen on, e.g. "0.0.0.0:8080"
smux *http.ServeMux
}

func (s *WebDAVServer) Run() {
if err := http.ListenAndServe(s.addr, s.smux); err != nil {
panic(err)
}
}

func NewWebDAVServer(addr string, handlerConfigs []*HandlerConfig) *WebDAVServer {
sMux := http.NewServeMux()

handlers := make(map[string]*Handler) // URL prefix -> Handler
handlers := make(map[string]*handler) // URL prefix -> Handler
for _, cfg := range handlerConfigs {
h := NewHandler(cfg)
handlers[cfg.Prefix] = h
}

// single dav mode: if there is only one handler and its prefix is "/", route all requests to it
enableSingleDavMode := false
var singleHandler *Handler
var singleHandler *handler
if len(handlers) == 1 {
for _, h := range handlers {
if h.prefix == "/" {
Expand Down Expand Up @@ -159,3 +76,9 @@ func NewWebDAVServer(addr string, handlerConfigs []*HandlerConfig) *WebDAVServer
smux: sMux,
}
}

func (s *WebDAVServer) Run() {
if err := http.ListenAndServe(s.addr, s.smux); err != nil {
panic(err)
}
}

0 comments on commit 0547fcc

Please sign in to comment.