srvdav/main.go

119 lines
3.9 KiB
Go

package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"time"
"github.com/abbot/go-http-auth"
caldav "github.com/samedi/caldav-go"
"golang.org/x/net/webdav"
)
var (
flPort = flag.Int("port", 9999, "server port")
flCert = flag.String("cert", "", "server SSL cert (both -cert and -key must be present to use SSL). See `go run $(go env GOROOT)/src/crypto/tls/generate_cert.go -h` to generate development cert/key")
flKey = flag.String("key", "", "server SSL key")
flHtpasswd = flag.String("htpasswd", "", "htpasswd file for auth (must be present to use auth) See htpasswd(1) to create this file.")
flCalDav = flag.String("caldav", "", "local path to store caldav data ('' means no caldav is served)")
)
func main() {
flag.Parse()
if flag.NArg() == 0 {
log.Fatal("One argument required. Please provide path to serve (you can use a special keyword of 'mem' to serve an in-memory filesystem)")
}
var fs webdav.FileSystem
if flag.Args()[0] == "mem" {
fs = webdav.NewMemFS()
} else {
fs = NewPassThroughFS(flag.Args()[0])
}
log.SetFlags(0)
h := &webdav.Handler{
FileSystem: fs,
LockSystem: webdav.NewMemLS(),
Logger: func(r *http.Request, err error) {
t := time.Now()
tStamp := fmt.Sprintf("%d.%9.9d", t.Unix(), t.Nanosecond())
switch r.Method {
case "COPY", "MOVE":
dst := ""
if u, err := url.Parse(r.Header.Get("Destination")); err == nil {
dst = u.Path
}
o := r.Header.Get("Overwrite")
log.Printf("%-21s%-25s%-10s%-30s%-30so=%-2s%v", tStamp, r.RemoteAddr, r.Method, r.URL.Path, dst, o, err)
default:
log.Printf("%-21s%-25s%-10s%-30s%v", tStamp, r.RemoteAddr, r.Method, r.URL.Path, err)
}
},
}
if *flHtpasswd != "" {
secret := auth.HtpasswdFileProvider(*flHtpasswd)
authenticator := auth.NewBasicAuthenticator("", secret)
authHandlerFunc := func(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
h.ServeHTTP(w, &r.Request)
}
if *flCalDav != "" {
http.HandleFunc("/caldav", authenticator.Wrap(func(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
caldav.RequestHandler(w, &r.Request)
}))
}
http.HandleFunc("/", authenticator.Wrap(authHandlerFunc))
} else {
log.Println("WARNING: connections are not authenticated. STRONGLY consider using -htpasswd.")
if *flCalDav != "" {
http.HandleFunc("/caldav", caldav.RequestHandler)
}
http.Handle("/", h)
}
addr := fmt.Sprintf(":%d", *flPort)
if *flCert != "" && *flKey != "" {
log.Printf("Serving HTTPS:// %v", addr)
log.Fatal(http.ListenAndServeTLS(addr, *flCert, *flKey, nil))
} else {
log.Println("WARNING: connections are not encrypted. STRONGLY consider using -cert/-key.")
log.Printf("Serving HTTP:// %v", addr)
log.Fatal(http.ListenAndServe(addr, nil))
}
}
func NewPassThroughFS(path string) webdav.FileSystem {
return &passThroughFS{root: path}
}
type passThroughFS struct {
root string
}
func (ptfs *passThroughFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
// TODO(vbatts) check for escaping the root directory
return os.Mkdir(filepath.Join(ptfs.root, name), perm)
}
func (ptfs *passThroughFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
// TODO(vbatts) check for escaping the root directory
return os.OpenFile(filepath.Join(ptfs.root, name), flag, perm)
}
func (ptfs *passThroughFS) RemoveAll(ctx context.Context, name string) error {
// TODO(vbatts) check for escaping the root directory
return os.RemoveAll(filepath.Join(ptfs.root, name))
}
func (ptfs *passThroughFS) Rename(ctx context.Context, oldName, newName string) error {
// TODO(vbatts) check for escaping the root directory
return os.Rename(filepath.Join(ptfs.root, oldName), filepath.Join(ptfs.root, newName))
}
func (ptfs *passThroughFS) Stat(ctx context.Context, name string) (os.FileInfo, error) {
// TODO(vbatts) check for escaping the root directory
return os.Stat(filepath.Join(ptfs.root, name))
}