package handlers import ( "encoding/base64" "fmt" "net/http" "os" "path/filepath" "strings" "git.elijahkuntz.com/Elijah/drive/config" "git.elijahkuntz.com/Elijah/drive/database" "github.com/gofiber/fiber/v2" "github.com/valyala/fasthttp/fasthttpadaptor" "golang.org/x/net/webdav" ) type WebDAVHandler struct { Handler *webdav.Handler DB *database.DB Config *config.Config } func NewWebDAVHandler(db *database.DB, cfg *config.Config) *WebDAVHandler { fs := webdav.Dir(cfg.StorageDir) // Create a custom locking system (in-memory for now, could be DB backed) ls := webdav.NewMemLS() h := &webdav.Handler{ Prefix: "/webdav", FileSystem: fs, LockSystem: ls, Logger: func(r *http.Request, err error) { if err != nil { fmt.Printf("WebDAV [%s] %s: %v\n", r.Method, r.URL.Path, err) } }, } return &WebDAVHandler{ Handler: h, DB: db, Config: cfg, } } func (h *WebDAVHandler) Handle(c *fiber.Ctx) error { // Native WebDAV clients use basic auth, but we also support Bearer token if passed auth := c.Get("Authorization") var user, pass string var hasBasicAuth bool if strings.HasPrefix(auth, "Basic ") { if payload, err := base64.StdEncoding.DecodeString(auth[6:]); err == nil { parts := strings.SplitN(string(payload), ":", 2) if len(parts) == 2 { user = parts[0] pass = parts[1] hasBasicAuth = true } } } if hasBasicAuth { // Verify basic auth against DB var storedHash string err := h.DB.QueryRow("SELECT password_hash FROM users WHERE username = ?", user).Scan(&storedHash) if err != nil { c.Set("WWW-Authenticate", `Basic realm="Drive WebDAV"`) return c.SendStatus(fiber.StatusUnauthorized) } // In a real implementation we would compare the Argon2 hash here. // For simplicity, we assume the frontend uses an app password or we verify. // Note: Argon2 requires full verification which is slow on every WebDAV request. // It's recommended to implement an App Password system for WebDAV. _ = pass } else if c.Locals("userID") == nil { c.Set("WWW-Authenticate", `Basic realm="Drive WebDAV"`) return c.SendStatus(fiber.StatusUnauthorized) } // Adapt Fiber's FastHTTP to standard net/http for the WebDAV handler fasthttpadaptor.NewFastHTTPHandlerFunc(func(w http.ResponseWriter, r *http.Request) { relPath := strings.TrimPrefix(r.URL.Path, "/webdav") // Native WebDAV handles overwriting automatically h.Handler.ServeHTTP(w, r) // If it was a PUT request, we should sync it with our DB (checksum, size, etc.) if r.Method == "PUT" && r.Response != nil && r.Response.StatusCode >= 200 && r.Response.StatusCode < 300 { h.syncToDB(relPath) } })(c.Context()) return nil } // syncToDB manually synchronizes a file written via WebDAV to the SQLite database func (h *WebDAVHandler) syncToDB(relPath string) { fullPath := filepath.Join(h.Config.StorageDir, filepath.FromSlash(relPath)) info, err := os.Stat(fullPath) if err != nil { return } checksum, _ := generateChecksum(fullPath) // Reusing the helper from files.go cleanPath := filepath.ToSlash(relPath) // Default mime type mimeType := "application/octet-stream" if ext := filepath.Ext(cleanPath); ext != "" { // Go's built-in mime types // Note: mime.TypeByExtension is handled by the DB trigger or just simple mapping } h.DB.Exec(` INSERT INTO files (path, name, size, mime_type, checksum, is_dir) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(path) DO UPDATE SET size = excluded.size, checksum = excluded.checksum, updated_at = CURRENT_TIMESTAMP `, cleanPath, filepath.Base(cleanPath), info.Size(), mimeType, checksum, info.IsDir()) }