Browse Source

Added wip webdav logic

Toby Chui 1 year ago
parent
commit
270f3f9470
4 changed files with 824 additions and 790 deletions
  1. 13 5
      mod/agi/externalReqHandler.go
  2. 61 54
      mod/filesystem/metadata/model.go
  3. 737 720
      mod/network/webdav/webdav.go
  4. 13 11
      mod/storage/webdav/webdav.go

+ 13 - 5
mod/agi/externalReqHandler.go

@@ -23,7 +23,7 @@ func (g *Gateway) ExtAPIHandler(w http.ResponseWriter, r *http.Request) {
 	sysdb := g.Option.UserHandler.GetDatabase()
 
 	if !sysdb.TableExists("external_agi") {
-		utils.SendErrorResponse(w, "Invalid Request")
+		http.Error(w, "invalid API request", http.StatusBadRequest)
 		return
 	}
 
@@ -33,7 +33,7 @@ func (g *Gateway) ExtAPIHandler(w http.ResponseWriter, r *http.Request) {
 
 	// check if it contains only two part, [rexec uuid]
 	if len(subpathElements) != 3 {
-		utils.SendErrorResponse(w, "Invalid Request")
+		http.Error(w, "invalid API request", http.StatusBadRequest)
 		return
 	}
 
@@ -41,7 +41,7 @@ func (g *Gateway) ExtAPIHandler(w http.ResponseWriter, r *http.Request) {
 	// get the info from the database
 	data, isExist := g.checkIfExternalEndpointExist(subpathElements[2])
 	if !isExist {
-		utils.SendErrorResponse(w, "Malform Request, invaild UUID given")
+		http.Error(w, "malformed request: invalid UUID given", http.StatusBadRequest)
 		return
 	}
 
@@ -51,12 +51,20 @@ func (g *Gateway) ExtAPIHandler(w http.ResponseWriter, r *http.Request) {
 	// get the userinfo and the realPath
 	userInfo, err := g.Option.UserHandler.GetUserInfoFromUsername(usernameFromDb)
 	if err != nil {
-		utils.SendErrorResponse(w, "Invalid username")
+		http.Error(w, "invalid request: API author no longer exists", http.StatusBadRequest)
 		return
 	}
 	fsh, realPath, err := static.VirtualPathToRealPath(pathFromDb, userInfo)
 	if err != nil {
-		utils.SendErrorResponse(w, "Invalid filepath")
+		http.Error(w, "invalid request: backend script path cannot be resolved", http.StatusBadRequest)
+		return
+	}
+
+	//Check if the script file still exists
+	if !fsh.FileSystemAbstraction.FileExists(realPath) {
+		//Script no longer exists
+		log.Println("[Remote AGI] ", pathFromDb, " cannot be found on "+realPath)
+		http.Error(w, "invalid request: backend script not exists", http.StatusBadRequest)
 		return
 	}
 

+ 61 - 54
mod/filesystem/metadata/model.go

@@ -1,54 +1,61 @@
-package metadata
-
-import (
-	"errors"
-	"image/jpeg"
-	"path/filepath"
-
-	"imuslab.com/arozos/mod/filesystem"
-	"imuslab.com/arozos/mod/filesystem/renderer"
-)
-
-func generateThumbnailForModel(fsh *filesystem.FileSystemHandler, cacheFolder string, file string, generateOnly bool) (string, error) {
-	if fsh.RequireBuffer {
-		return "", nil
-	}
-	fshAbs := fsh.FileSystemAbstraction
-
-	if !fshAbs.FileExists(file) {
-		//The user removed this file before the thumbnail is finished
-		return "", errors.New("Source not exists")
-	}
-
-	//Generate a render of the 3d model
-	outputFile := cacheFolder + filepath.Base(file) + ".jpg"
-	r := renderer.NewRenderer(renderer.RenderOption{
-		Color:           "#f2f542",
-		BackgroundColor: "#ffffff",
-		Width:           480,
-		Height:          480,
-	})
-
-	img, _ := r.RenderModel(file)
-	opt := jpeg.Options{
-		Quality: 90,
-	}
-
-	f, err := fshAbs.Create(outputFile)
-	if err != nil {
-		return "", err
-	}
-
-	jpeg.Encode(f, img, &opt)
-	f.Close()
-
-	if !generateOnly && fshAbs.FileExists(outputFile) {
-		//return the image as well
-		ctx, err := getImageAsBase64(fsh, outputFile)
-		return ctx, err
-	} else if !fshAbs.FileExists(outputFile) {
-		return "", errors.New("Image generation failed")
-	}
-	return "", nil
-
-}
+package metadata
+
+import (
+	"errors"
+	"image/jpeg"
+	"path/filepath"
+
+	"imuslab.com/arozos/mod/filesystem"
+	"imuslab.com/arozos/mod/filesystem/renderer"
+)
+
+func generateThumbnailForModel(fsh *filesystem.FileSystemHandler, cacheFolder string, file string, generateOnly bool) (string, error) {
+	if fsh.RequireBuffer {
+		return "", nil
+	}
+	fshAbs := fsh.FileSystemAbstraction
+
+	if !fshAbs.FileExists(file) {
+		//The user removed this file before the thumbnail is finished
+		return "", errors.New("Source not exists")
+	}
+
+	//Generate a render of the 3d model
+	outputFile := cacheFolder + filepath.Base(file) + ".jpg"
+	r := renderer.NewRenderer(renderer.RenderOption{
+		Color:           "#f2f542",
+		BackgroundColor: "#ffffff",
+		Width:           480,
+		Height:          480,
+	})
+
+	img, err := r.RenderModel(file)
+	if err != nil {
+		return "", err
+	}
+
+	opt := jpeg.Options{
+		Quality: 90,
+	}
+
+	f, err := fshAbs.Create(outputFile)
+	if err != nil {
+		return "", err
+	}
+
+	err = jpeg.Encode(f, img, &opt)
+	if err != nil {
+		return "", err
+	}
+	f.Close()
+
+	if !generateOnly && fshAbs.FileExists(outputFile) {
+		//return the image as well
+		ctx, err := getImageAsBase64(fsh, outputFile)
+		return ctx, err
+	} else if !fshAbs.FileExists(outputFile) {
+		return "", errors.New("Image generation failed")
+	}
+	return "", nil
+
+}

+ 737 - 720
mod/network/webdav/webdav.go

@@ -1,720 +1,737 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package webdav provides a WebDAV server implementation.
-package webdav // import "golang.org/x/net/webdav"
-
-import (
-	"errors"
-	"fmt"
-	"io"
-	"net/http"
-	"net/url"
-	"os"
-	"path"
-	"strings"
-	"time"
-)
-
-type Handler struct {
-	// Prefix is the URL path prefix to strip from WebDAV resource paths.
-	Prefix string
-	// FileSystem is the virtual file system.
-	FileSystem FileSystem
-	// LockSystem is the lock management system.
-	LockSystem LockSystem
-	// Logger is an optional error logger. If non-nil, it will be called
-	// for all HTTP requests.
-	Logger func(*http.Request, error)
-	//External Event Listener for the webdev request
-	RequestEventListener func(path string)
-}
-
-func (h *Handler) stripPrefix(p string) (string, int, error) {
-	if h.Prefix == "" {
-		return p, http.StatusOK, nil
-	}
-	if r := strings.TrimPrefix(p, h.Prefix); len(r) < len(p) {
-		return r, http.StatusOK, nil
-	}
-	return p, http.StatusNotFound, errPrefixMismatch
-}
-
-func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	status, err := http.StatusBadRequest, errUnsupportedMethod
-	if h.FileSystem == nil {
-		status, err = http.StatusInternalServerError, errNoFileSystem
-	} else if h.LockSystem == nil {
-		status, err = http.StatusInternalServerError, errNoLockSystem
-	} else {
-		switch r.Method {
-		case "OPTIONS":
-			status, err = h.handleOptions(w, r)
-		case "GET", "HEAD", "POST":
-			status, err = h.handleGetHeadPost(w, r)
-		case "DELETE":
-			status, err = h.handleDelete(w, r)
-		case "PUT":
-			status, err = h.handlePut(w, r)
-		case "MKCOL":
-			status, err = h.handleMkcol(w, r)
-		case "COPY", "MOVE":
-			status, err = h.handleCopyMove(w, r)
-		case "LOCK":
-			status, err = h.handleLock(w, r)
-		case "UNLOCK":
-			status, err = h.handleUnlock(w, r)
-		case "PROPFIND":
-			status, err = h.handlePropfind(w, r)
-		case "PROPPATCH":
-			status, err = h.handleProppatch(w, r)
-		}
-	}
-
-	if status != 0 {
-		w.WriteHeader(status)
-		if status != http.StatusNoContent {
-			w.Write([]byte(StatusText(status)))
-		}
-	}
-	if h.Logger != nil {
-		h.Logger(r, err)
-	}
-}
-
-func (h *Handler) lock(now time.Time, root string) (token string, status int, err error) {
-	token, err = h.LockSystem.Create(now, LockDetails{
-		Root:      root,
-		Duration:  infiniteTimeout,
-		ZeroDepth: true,
-	})
-	if err != nil {
-		if err == ErrLocked {
-			return "", StatusLocked, err
-		}
-		return "", http.StatusInternalServerError, err
-	}
-	return token, 0, nil
-}
-
-func (h *Handler) confirmLocks(r *http.Request, src, dst string) (release func(), status int, err error) {
-	hdr := r.Header.Get("If")
-	if hdr == "" {
-		// An empty If header means that the client hasn't previously created locks.
-		// Even if this client doesn't care about locks, we still need to check that
-		// the resources aren't locked by another client, so we create temporary
-		// locks that would conflict with another client's locks. These temporary
-		// locks are unlocked at the end of the HTTP request.
-		now, srcToken, dstToken := time.Now(), "", ""
-		if src != "" {
-			srcToken, status, err = h.lock(now, src)
-			if err != nil {
-				return nil, status, err
-			}
-		}
-		if dst != "" {
-			dstToken, status, err = h.lock(now, dst)
-			if err != nil {
-				if srcToken != "" {
-					h.LockSystem.Unlock(now, srcToken)
-				}
-				return nil, status, err
-			}
-		}
-
-		return func() {
-			if dstToken != "" {
-				h.LockSystem.Unlock(now, dstToken)
-			}
-			if srcToken != "" {
-				h.LockSystem.Unlock(now, srcToken)
-			}
-		}, 0, nil
-	}
-
-	ih, ok := parseIfHeader(hdr)
-	if !ok {
-		return nil, http.StatusBadRequest, errInvalidIfHeader
-	}
-	// ih is a disjunction (OR) of ifLists, so any ifList will do.
-	for _, l := range ih.lists {
-		lsrc := l.resourceTag
-		if lsrc == "" {
-			lsrc = src
-		} else {
-			u, err := url.Parse(lsrc)
-			if err != nil {
-				continue
-			}
-			if u.Host != r.Host {
-				continue
-			}
-			lsrc, status, err = h.stripPrefix(u.Path)
-			if err != nil {
-				return nil, status, err
-			}
-		}
-		release, err = h.LockSystem.Confirm(time.Now(), lsrc, dst, l.conditions...)
-		if err == ErrConfirmationFailed {
-			continue
-		}
-		if err != nil {
-			return nil, http.StatusInternalServerError, err
-		}
-		return release, 0, nil
-	}
-	// Section 10.4.1 says that "If this header is evaluated and all state lists
-	// fail, then the request must fail with a 412 (Precondition Failed) status."
-	// We follow the spec even though the cond_put_corrupt_token test case from
-	// the litmus test warns on seeing a 412 instead of a 423 (Locked).
-	return nil, http.StatusPreconditionFailed, ErrLocked
-}
-
-func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status int, err error) {
-	reqPath, status, err := h.stripPrefix(r.URL.Path)
-	if err != nil {
-		return status, err
-	}
-	ctx := r.Context()
-	allow := "OPTIONS, LOCK, PUT, MKCOL"
-	if fi, err := h.FileSystem.Stat(ctx, reqPath); err == nil {
-		if fi.IsDir() {
-			allow = "OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND"
-		} else {
-			allow = "OPTIONS, LOCK, GET, HEAD, POST, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND, PUT"
-		}
-	}
-	w.Header().Set("Allow", allow)
-	// http://www.webdav.org/specs/rfc4918.html#dav.compliance.classes
-	w.Header().Set("DAV", "1, 2")
-	// http://msdn.microsoft.com/en-au/library/cc250217.aspx
-	w.Header().Set("MS-Author-Via", "DAV")
-	return 0, nil
-}
-
-func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (status int, err error) {
-	reqPath, status, err := h.stripPrefix(r.URL.Path)
-	if err != nil {
-		return status, err
-	}
-	// TODO: check locks for read-only access??
-	ctx := r.Context()
-	f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDONLY, 0)
-	if err != nil {
-		return http.StatusNotFound, err
-	}
-	defer f.Close()
-	fi, err := f.Stat()
-	if err != nil {
-		return http.StatusNotFound, err
-	}
-	if fi.IsDir() {
-		return http.StatusMethodNotAllowed, nil
-	}
-	etag, err := findETag(ctx, h.FileSystem, h.LockSystem, reqPath, fi)
-	if err != nil {
-		return http.StatusInternalServerError, err
-	}
-	w.Header().Set("ETag", etag)
-	// Let ServeContent determine the Content-Type header.
-	http.ServeContent(w, r, reqPath, fi.ModTime(), f)
-	return 0, nil
-}
-
-func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status int, err error) {
-	reqPath, status, err := h.stripPrefix(r.URL.Path)
-	if err != nil {
-		return status, err
-	}
-	release, status, err := h.confirmLocks(r, reqPath, "")
-	if err != nil {
-		return status, err
-	}
-	defer release()
-
-	ctx := r.Context()
-
-	// TODO: return MultiStatus where appropriate.
-
-	// "godoc os RemoveAll" says that "If the path does not exist, RemoveAll
-	// returns nil (no error)." WebDAV semantics are that it should return a
-	// "404 Not Found". We therefore have to Stat before we RemoveAll.
-	if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
-		if os.IsNotExist(err) {
-			return http.StatusNotFound, err
-		}
-		return http.StatusMethodNotAllowed, err
-	}
-	if err := h.FileSystem.RemoveAll(ctx, reqPath); err != nil {
-		return http.StatusMethodNotAllowed, err
-	}
-	return http.StatusNoContent, nil
-}
-
-func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int, err error) {
-	reqPath, status, err := h.stripPrefix(r.URL.Path)
-	if err != nil {
-		return status, err
-	}
-	release, status, err := h.confirmLocks(r, reqPath, "")
-	if err != nil {
-		return status, err
-	}
-	defer release()
-	// TODO(rost): Support the If-Match, If-None-Match headers? See bradfitz'
-	// comments in http.checkEtag.
-	ctx := r.Context()
-
-	f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
-	if err != nil {
-		return http.StatusNotFound, err
-	}
-	_, copyErr := io.Copy(f, r.Body)
-	fi, statErr := f.Stat()
-	closeErr := f.Close()
-	// TODO(rost): Returning 405 Method Not Allowed might not be appropriate.
-	if copyErr != nil {
-		return http.StatusMethodNotAllowed, copyErr
-	}
-	if statErr != nil {
-		return http.StatusMethodNotAllowed, statErr
-	}
-	if closeErr != nil {
-		return http.StatusMethodNotAllowed, closeErr
-	}
-	etag, err := findETag(ctx, h.FileSystem, h.LockSystem, reqPath, fi)
-	if err != nil {
-		return http.StatusInternalServerError, err
-	}
-	w.Header().Set("ETag", etag)
-	return http.StatusCreated, nil
-}
-
-func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status int, err error) {
-	reqPath, status, err := h.stripPrefix(r.URL.Path)
-	if err != nil {
-		return status, err
-	}
-	release, status, err := h.confirmLocks(r, reqPath, "")
-	if err != nil {
-		return status, err
-	}
-	defer release()
-
-	ctx := r.Context()
-
-	if r.ContentLength > 0 {
-		return http.StatusUnsupportedMediaType, nil
-	}
-	if err := h.FileSystem.Mkdir(ctx, reqPath, 0777); err != nil {
-		if os.IsNotExist(err) {
-			return http.StatusConflict, err
-		}
-		return http.StatusMethodNotAllowed, err
-	}
-	return http.StatusCreated, nil
-}
-
-func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status int, err error) {
-	hdr := r.Header.Get("Destination")
-	if hdr == "" {
-		return http.StatusBadRequest, errInvalidDestination
-	}
-	u, err := url.Parse(hdr)
-	if err != nil {
-		return http.StatusBadRequest, errInvalidDestination
-	}
-	if u.Host != "" && u.Host != r.Host {
-		return http.StatusBadGateway, errInvalidDestination
-	}
-
-	src, status, err := h.stripPrefix(r.URL.Path)
-	if err != nil {
-		return status, err
-	}
-
-	dst, status, err := h.stripPrefix(u.Path)
-	if err != nil {
-		return status, err
-	}
-
-	if dst == "" {
-		return http.StatusBadGateway, errInvalidDestination
-	}
-	if dst == src {
-		return http.StatusForbidden, errDestinationEqualsSource
-	}
-
-	ctx := r.Context()
-
-	if r.Method == "COPY" {
-		// Section 7.5.1 says that a COPY only needs to lock the destination,
-		// not both destination and source. Strictly speaking, this is racy,
-		// even though a COPY doesn't modify the source, if a concurrent
-		// operation modifies the source. However, the litmus test explicitly
-		// checks that COPYing a locked-by-another source is OK.
-		release, status, err := h.confirmLocks(r, "", dst)
-		if err != nil {
-			return status, err
-		}
-		defer release()
-
-		// Section 9.8.3 says that "The COPY method on a collection without a Depth
-		// header must act as if a Depth header with value "infinity" was included".
-		depth := infiniteDepth
-		if hdr := r.Header.Get("Depth"); hdr != "" {
-			depth = parseDepth(hdr)
-			if depth != 0 && depth != infiniteDepth {
-				// Section 9.8.3 says that "A client may submit a Depth header on a
-				// COPY on a collection with a value of "0" or "infinity"."
-				return http.StatusBadRequest, errInvalidDepth
-			}
-		}
-		return copyFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") != "F", depth, 0)
-	}
-
-	release, status, err := h.confirmLocks(r, src, dst)
-	if err != nil {
-		return status, err
-	}
-	defer release()
-
-	// Section 9.9.2 says that "The MOVE method on a collection must act as if
-	// a "Depth: infinity" header was used on it. A client must not submit a
-	// Depth header on a MOVE on a collection with any value but "infinity"."
-	if hdr := r.Header.Get("Depth"); hdr != "" {
-		if parseDepth(hdr) != infiniteDepth {
-			return http.StatusBadRequest, errInvalidDepth
-		}
-	}
-	return moveFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") == "T")
-}
-
-func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus int, retErr error) {
-	duration, err := parseTimeout(r.Header.Get("Timeout"))
-	if err != nil {
-		return http.StatusBadRequest, err
-	}
-	li, status, err := readLockInfo(r.Body)
-	if err != nil {
-		return status, err
-	}
-
-	ctx := r.Context()
-	token, ld, now, created := "", LockDetails{}, time.Now(), false
-	if li == (lockInfo{}) {
-		// An empty lockInfo means to refresh the lock.
-		ih, ok := parseIfHeader(r.Header.Get("If"))
-		if !ok {
-			return http.StatusBadRequest, errInvalidIfHeader
-		}
-		if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 {
-			token = ih.lists[0].conditions[0].Token
-		}
-		if token == "" {
-			return http.StatusBadRequest, errInvalidLockToken
-		}
-		ld, err = h.LockSystem.Refresh(now, token, duration)
-		if err != nil {
-			if err == ErrNoSuchLock {
-				return http.StatusPreconditionFailed, err
-			}
-			return http.StatusInternalServerError, err
-		}
-
-	} else {
-		// Section 9.10.3 says that "If no Depth header is submitted on a LOCK request,
-		// then the request MUST act as if a "Depth:infinity" had been submitted."
-		depth := infiniteDepth
-		if hdr := r.Header.Get("Depth"); hdr != "" {
-			depth = parseDepth(hdr)
-			if depth != 0 && depth != infiniteDepth {
-				// Section 9.10.3 says that "Values other than 0 or infinity must not be
-				// used with the Depth header on a LOCK method".
-				return http.StatusBadRequest, errInvalidDepth
-			}
-		}
-		reqPath, status, err := h.stripPrefix(r.URL.Path)
-		if err != nil {
-			return status, err
-		}
-		ld = LockDetails{
-			Root:      reqPath,
-			Duration:  duration,
-			OwnerXML:  li.Owner.InnerXML,
-			ZeroDepth: depth == 0,
-		}
-		token, err = h.LockSystem.Create(now, ld)
-		if err != nil {
-			if err == ErrLocked {
-				return StatusLocked, err
-			}
-			return http.StatusInternalServerError, err
-		}
-		defer func() {
-			if retErr != nil {
-				h.LockSystem.Unlock(now, token)
-			}
-		}()
-
-		// Create the resource if it didn't previously exist.
-		if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
-			f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
-			if err != nil {
-				// TODO: detect missing intermediate dirs and return http.StatusConflict?
-				return http.StatusInternalServerError, err
-			}
-			f.Close()
-			created = true
-		}
-
-		// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
-		// Lock-Token value is a Coded-URL. We add angle brackets.
-		w.Header().Set("Lock-Token", "<"+token+">")
-	}
-
-	w.Header().Set("Content-Type", "application/xml; charset=utf-8")
-	if created {
-		// This is "w.WriteHeader(http.StatusCreated)" and not "return
-		// http.StatusCreated, nil" because we write our own (XML) response to w
-		// and Handler.ServeHTTP would otherwise write "Created".
-		w.WriteHeader(http.StatusCreated)
-	}
-	writeLockInfo(w, token, ld)
-	return 0, nil
-}
-
-func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request) (status int, err error) {
-	// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
-	// Lock-Token value is a Coded-URL. We strip its angle brackets.
-	t := r.Header.Get("Lock-Token")
-	if len(t) < 2 || t[0] != '<' || t[len(t)-1] != '>' {
-		return http.StatusBadRequest, errInvalidLockToken
-	}
-	t = t[1 : len(t)-1]
-
-	switch err = h.LockSystem.Unlock(time.Now(), t); err {
-	case nil:
-		return http.StatusNoContent, err
-	case ErrForbidden:
-		return http.StatusForbidden, err
-	case ErrLocked:
-		return StatusLocked, err
-	case ErrNoSuchLock:
-		return http.StatusConflict, err
-	default:
-		return http.StatusInternalServerError, err
-	}
-}
-
-func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status int, err error) {
-	reqPath, status, err := h.stripPrefix(r.URL.Path)
-	if err != nil {
-		return status, err
-	}
-	ctx := r.Context()
-	fi, err := h.FileSystem.Stat(ctx, reqPath)
-	if err != nil {
-		if os.IsNotExist(err) {
-			return http.StatusNotFound, err
-		}
-		return http.StatusMethodNotAllowed, err
-	}
-	depth := infiniteDepth
-	if hdr := r.Header.Get("Depth"); hdr != "" {
-		depth = parseDepth(hdr)
-		if depth == invalidDepth {
-			return http.StatusBadRequest, errInvalidDepth
-		}
-	}
-	pf, status, err := readPropfind(r.Body)
-	if err != nil {
-		return status, err
-	}
-
-	mw := multistatusWriter{w: w}
-
-	walkFn := func(reqPath string, info os.FileInfo, err error) error {
-		if err != nil {
-			return nil
-		}
-
-		//Arozos custom properties
-		/*
-			if len(reqPath) > 1 && filepath.Base(reqPath)[:1] == "." {
-				//Hidden file. Do not show
-				return nil
-			}
-		*/
-
-		var pstats []Propstat
-		if pf.Propname != nil {
-			pnames, err := propnames(ctx, h.FileSystem, h.LockSystem, reqPath)
-			if err != nil {
-				return nil
-			}
-			pstat := Propstat{Status: http.StatusOK}
-			for _, xmlname := range pnames {
-				pstat.Props = append(pstat.Props, Property{XMLName: xmlname})
-			}
-			pstats = append(pstats, pstat)
-		} else if pf.Allprop != nil {
-			pstats, err = allprop(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
-		} else {
-			pstats, err = props(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
-		}
-		if err != nil {
-			return nil
-		}
-		href := path.Join(h.Prefix, reqPath)
-		if href != "/" && info.IsDir() {
-			href += "/"
-		}
-		return mw.write(makePropstatResponse(href, pstats))
-	}
-
-	if h.RequestEventListener != nil {
-		h.RequestEventListener(reqPath)
-	}
-	walkErr := walkFS(ctx, h.FileSystem, depth, reqPath, fi, walkFn)
-	closeErr := mw.close()
-	if walkErr != nil {
-		return http.StatusInternalServerError, walkErr
-	}
-	if closeErr != nil {
-		return http.StatusInternalServerError, closeErr
-	}
-	return 0, nil
-}
-
-func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (status int, err error) {
-	reqPath, status, err := h.stripPrefix(r.URL.Path)
-	if err != nil {
-		return status, err
-	}
-	release, status, err := h.confirmLocks(r, reqPath, "")
-	if err != nil {
-		return status, err
-	}
-	defer release()
-
-	ctx := r.Context()
-
-	if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
-		if os.IsNotExist(err) {
-			return http.StatusNotFound, err
-		}
-		return http.StatusMethodNotAllowed, err
-	}
-	patches, status, err := readProppatch(r.Body)
-	if err != nil {
-		return status, err
-	}
-	pstats, err := patch(ctx, h.FileSystem, h.LockSystem, reqPath, patches)
-	if err != nil {
-		return http.StatusInternalServerError, err
-	}
-	mw := multistatusWriter{w: w}
-	writeErr := mw.write(makePropstatResponse(r.URL.Path, pstats))
-	closeErr := mw.close()
-	if writeErr != nil {
-		return http.StatusInternalServerError, writeErr
-	}
-	if closeErr != nil {
-		return http.StatusInternalServerError, closeErr
-	}
-	return 0, nil
-}
-
-func makePropstatResponse(href string, pstats []Propstat) *response {
-	resp := response{
-		Href:     []string{(&url.URL{Path: href}).EscapedPath()},
-		Propstat: make([]propstat, 0, len(pstats)),
-	}
-	for _, p := range pstats {
-		var xmlErr *xmlError
-		if p.XMLError != "" {
-			xmlErr = &xmlError{InnerXML: []byte(p.XMLError)}
-		}
-		resp.Propstat = append(resp.Propstat, propstat{
-			Status:              fmt.Sprintf("HTTP/1.1 %d %s", p.Status, StatusText(p.Status)),
-			Prop:                p.Props,
-			ResponseDescription: p.ResponseDescription,
-			Error:               xmlErr,
-		})
-	}
-	return &resp
-}
-
-const (
-	infiniteDepth = -1
-	invalidDepth  = -2
-)
-
-// parseDepth maps the strings "0", "1" and "infinity" to 0, 1 and
-// infiniteDepth. Parsing any other string returns invalidDepth.
-//
-// Different WebDAV methods have further constraints on valid depths:
-//	- PROPFIND has no further restrictions, as per section 9.1.
-//	- COPY accepts only "0" or "infinity", as per section 9.8.3.
-//	- MOVE accepts only "infinity", as per section 9.9.2.
-//	- LOCK accepts only "0" or "infinity", as per section 9.10.3.
-// These constraints are enforced by the handleXxx methods.
-func parseDepth(s string) int {
-	switch s {
-	case "0":
-		return 0
-	case "1":
-		return 1
-	case "infinity":
-		return infiniteDepth
-	}
-	return invalidDepth
-}
-
-// http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11
-const (
-	StatusMulti               = 207
-	StatusUnprocessableEntity = 422
-	StatusLocked              = 423
-	StatusFailedDependency    = 424
-	StatusInsufficientStorage = 507
-)
-
-func StatusText(code int) string {
-	switch code {
-	case StatusMulti:
-		return "Multi-Status"
-	case StatusUnprocessableEntity:
-		return "Unprocessable Entity"
-	case StatusLocked:
-		return "Locked"
-	case StatusFailedDependency:
-		return "Failed Dependency"
-	case StatusInsufficientStorage:
-		return "Insufficient Storage"
-	}
-	return http.StatusText(code)
-}
-
-var (
-	errDestinationEqualsSource = errors.New("webdav: destination equals source")
-	errDirectoryNotEmpty       = errors.New("webdav: directory not empty")
-	errInvalidDepth            = errors.New("webdav: invalid depth")
-	errInvalidDestination      = errors.New("webdav: invalid destination")
-	errInvalidIfHeader         = errors.New("webdav: invalid If header")
-	errInvalidLockInfo         = errors.New("webdav: invalid lock info")
-	errInvalidLockToken        = errors.New("webdav: invalid lock token")
-	errInvalidPropfind         = errors.New("webdav: invalid propfind")
-	errInvalidProppatch        = errors.New("webdav: invalid proppatch")
-	errInvalidResponse         = errors.New("webdav: invalid response")
-	errInvalidTimeout          = errors.New("webdav: invalid timeout")
-	errNoFileSystem            = errors.New("webdav: no file system")
-	errNoLockSystem            = errors.New("webdav: no lock system")
-	errNotADirectory           = errors.New("webdav: not a directory")
-	errPrefixMismatch          = errors.New("webdav: prefix mismatch")
-	errRecursionTooDeep        = errors.New("webdav: recursion too deep")
-	errUnsupportedLockInfo     = errors.New("webdav: unsupported lock info")
-	errUnsupportedMethod       = errors.New("webdav: unsupported method")
-)
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package webdav provides a WebDAV server implementation.
+package webdav // import "golang.org/x/net/webdav"
+
+import (
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"os"
+	"path"
+	"strings"
+	"time"
+)
+
+type Handler struct {
+	// Prefix is the URL path prefix to strip from WebDAV resource paths.
+	Prefix string
+	// FileSystem is the virtual file system.
+	FileSystem FileSystem
+	// LockSystem is the lock management system.
+	LockSystem LockSystem
+	// Logger is an optional error logger. If non-nil, it will be called
+	// for all HTTP requests.
+	Logger func(*http.Request, error)
+	//External Event Listener for the webdev request
+	RequestEventListener func(path string)
+}
+
+func (h *Handler) stripPrefix(p string) (string, int, error) {
+	if h.Prefix == "" {
+		return p, http.StatusOK, nil
+	}
+	if r := strings.TrimPrefix(p, h.Prefix); len(r) < len(p) {
+		return r, http.StatusOK, nil
+	}
+	return p, http.StatusNotFound, errPrefixMismatch
+}
+
+func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	status, err := http.StatusBadRequest, errUnsupportedMethod
+	if h.FileSystem == nil {
+		status, err = http.StatusInternalServerError, errNoFileSystem
+	} else if h.LockSystem == nil {
+		status, err = http.StatusInternalServerError, errNoLockSystem
+	} else {
+		switch r.Method {
+		case "OPTIONS":
+			status, err = h.handleOptions(w, r)
+		case "GET", "HEAD", "POST":
+			status, err = h.handleGetHeadPost(w, r)
+		case "DELETE":
+			status, err = h.handleDelete(w, r)
+		case "PUT":
+			fmt.Println("PUTTING")
+			status, err = h.handlePut(w, r)
+			fmt.Println(status, err)
+		case "MKCOL":
+			status, err = h.handleMkcol(w, r)
+		case "COPY", "MOVE":
+			status, err = h.handleCopyMove(w, r)
+		case "LOCK":
+			status, err = h.handleLock(w, r)
+		case "UNLOCK":
+			status, err = h.handleUnlock(w, r)
+		case "PROPFIND":
+			status, err = h.handlePropfind(w, r)
+		case "PROPPATCH":
+			status, err = h.handleProppatch(w, r)
+		}
+	}
+
+	if status != 0 {
+		w.WriteHeader(status)
+		if status != http.StatusNoContent {
+			w.Write([]byte(StatusText(status)))
+		}
+	}
+	if h.Logger != nil {
+		h.Logger(r, err)
+	}
+}
+
+func (h *Handler) lock(now time.Time, root string) (token string, status int, err error) {
+	token, err = h.LockSystem.Create(now, LockDetails{
+		Root:      root,
+		Duration:  infiniteTimeout,
+		ZeroDepth: true,
+	})
+	if err != nil {
+		if err == ErrLocked {
+			return "", StatusLocked, err
+		}
+		return "", http.StatusInternalServerError, err
+	}
+	return token, 0, nil
+}
+
+func (h *Handler) confirmLocks(r *http.Request, src, dst string) (release func(), status int, err error) {
+	hdr := r.Header.Get("If")
+	if hdr == "" {
+		// An empty If header means that the client hasn't previously created locks.
+		// Even if this client doesn't care about locks, we still need to check that
+		// the resources aren't locked by another client, so we create temporary
+		// locks that would conflict with another client's locks. These temporary
+		// locks are unlocked at the end of the HTTP request.
+		now, srcToken, dstToken := time.Now(), "", ""
+		if src != "" {
+			srcToken, status, err = h.lock(now, src)
+			if err != nil {
+				return nil, status, err
+			}
+		}
+		if dst != "" {
+			dstToken, status, err = h.lock(now, dst)
+			if err != nil {
+				if srcToken != "" {
+					h.LockSystem.Unlock(now, srcToken)
+				}
+				return nil, status, err
+			}
+		}
+
+		return func() {
+			if dstToken != "" {
+				h.LockSystem.Unlock(now, dstToken)
+			}
+			if srcToken != "" {
+				h.LockSystem.Unlock(now, srcToken)
+			}
+		}, 0, nil
+	}
+
+	ih, ok := parseIfHeader(hdr)
+	if !ok {
+		return nil, http.StatusBadRequest, errInvalidIfHeader
+	}
+	// ih is a disjunction (OR) of ifLists, so any ifList will do.
+	for _, l := range ih.lists {
+		lsrc := l.resourceTag
+		if lsrc == "" {
+			lsrc = src
+		} else {
+			u, err := url.Parse(lsrc)
+			if err != nil {
+				continue
+			}
+			if u.Host != r.Host {
+				continue
+			}
+			lsrc, status, err = h.stripPrefix(u.Path)
+			if err != nil {
+				return nil, status, err
+			}
+		}
+		release, err = h.LockSystem.Confirm(time.Now(), lsrc, dst, l.conditions...)
+		if err == ErrConfirmationFailed {
+			continue
+		}
+		if err != nil {
+			return nil, http.StatusInternalServerError, err
+		}
+		return release, 0, nil
+	}
+	// Section 10.4.1 says that "If this header is evaluated and all state lists
+	// fail, then the request must fail with a 412 (Precondition Failed) status."
+	// We follow the spec even though the cond_put_corrupt_token test case from
+	// the litmus test warns on seeing a 412 instead of a 423 (Locked).
+	return nil, http.StatusPreconditionFailed, ErrLocked
+}
+
+func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status int, err error) {
+	reqPath, status, err := h.stripPrefix(r.URL.Path)
+	if err != nil {
+		return status, err
+	}
+	ctx := r.Context()
+	allow := "OPTIONS, LOCK, PUT, MKCOL"
+	if fi, err := h.FileSystem.Stat(ctx, reqPath); err == nil {
+		if fi.IsDir() {
+			allow = "OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND"
+		} else {
+			allow = "OPTIONS, LOCK, GET, HEAD, POST, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND, PUT"
+		}
+	}
+	w.Header().Set("Allow", allow)
+	// http://www.webdav.org/specs/rfc4918.html#dav.compliance.classes
+	w.Header().Set("DAV", "1, 2")
+	// http://msdn.microsoft.com/en-au/library/cc250217.aspx
+	w.Header().Set("MS-Author-Via", "DAV")
+	return 0, nil
+}
+
+func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (status int, err error) {
+	reqPath, status, err := h.stripPrefix(r.URL.Path)
+	if err != nil {
+		return status, err
+	}
+	// TODO: check locks for read-only access??
+	ctx := r.Context()
+	f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDONLY, 0)
+	if err != nil {
+		return http.StatusNotFound, err
+	}
+	defer f.Close()
+	fi, err := f.Stat()
+	if err != nil {
+		return http.StatusNotFound, err
+	}
+	if fi.IsDir() {
+		return http.StatusMethodNotAllowed, nil
+	}
+	etag, err := findETag(ctx, h.FileSystem, h.LockSystem, reqPath, fi)
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+	w.Header().Set("ETag", etag)
+	// Let ServeContent determine the Content-Type header.
+	http.ServeContent(w, r, reqPath, fi.ModTime(), f)
+	return 0, nil
+}
+
+func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status int, err error) {
+	reqPath, status, err := h.stripPrefix(r.URL.Path)
+	if err != nil {
+		return status, err
+	}
+	release, status, err := h.confirmLocks(r, reqPath, "")
+	if err != nil {
+		return status, err
+	}
+	defer release()
+
+	ctx := r.Context()
+
+	// TODO: return MultiStatus where appropriate.
+
+	// "godoc os RemoveAll" says that "If the path does not exist, RemoveAll
+	// returns nil (no error)." WebDAV semantics are that it should return a
+	// "404 Not Found". We therefore have to Stat before we RemoveAll.
+	if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
+		if os.IsNotExist(err) {
+			return http.StatusNotFound, err
+		}
+		return http.StatusMethodNotAllowed, err
+	}
+	if err := h.FileSystem.RemoveAll(ctx, reqPath); err != nil {
+		return http.StatusMethodNotAllowed, err
+	}
+	return http.StatusNoContent, nil
+}
+
+// Check if the request was from a windows client
+func isWindowsClient(r *http.Request) bool {
+	return r.Header["User-Agent"] != nil && strings.Contains(r.Header["User-Agent"][0], "Microsoft-WebDAV-MiniRedir")
+}
+
+func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int, err error) {
+	fmt.Println("breakpoint 1")
+	reqPath, status, err := h.stripPrefix(r.URL.Path)
+	if err != nil {
+		return status, err
+	}
+	fmt.Println("breakpoint 2")
+	release, status, err := h.confirmLocks(r, reqPath, "")
+	if err != nil && !isWindowsClient(r) {
+		return status, err
+	} else if err != nil && isWindowsClient(r) {
+		time.Sleep(1 * time.Second)
+	}
+	defer release()
+	// TODO(rost): Support the If-Match, If-None-Match headers? See bradfitz'
+	// comments in http.checkEtag.
+	ctx := r.Context()
+	fmt.Println("breakpoint 3")
+
+	f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
+	if err != nil {
+		fmt.Println("1", err)
+		return http.StatusNotFound, err
+	}
+	_, copyErr := io.Copy(f, r.Body)
+	fi, statErr := f.Stat()
+	closeErr := f.Close()
+	// TODO(rost): Returning 405 Method Not Allowed might not be appropriate.
+	if copyErr != nil {
+		return http.StatusMethodNotAllowed, copyErr
+	}
+	if statErr != nil {
+		return http.StatusMethodNotAllowed, statErr
+	}
+	if closeErr != nil {
+		return http.StatusMethodNotAllowed, closeErr
+	}
+
+	etag, err := findETag(ctx, h.FileSystem, h.LockSystem, reqPath, fi)
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+	fmt.Println(w)
+	w.Header().Set("ETag", etag)
+
+	return http.StatusCreated, nil
+}
+
+func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status int, err error) {
+	reqPath, status, err := h.stripPrefix(r.URL.Path)
+	if err != nil {
+		return status, err
+	}
+	release, status, err := h.confirmLocks(r, reqPath, "")
+	if err != nil {
+		return status, err
+	}
+	defer release()
+
+	ctx := r.Context()
+
+	if r.ContentLength > 0 {
+		return http.StatusUnsupportedMediaType, nil
+	}
+	if err := h.FileSystem.Mkdir(ctx, reqPath, 0777); err != nil {
+		if os.IsNotExist(err) {
+			return http.StatusConflict, err
+		}
+		return http.StatusMethodNotAllowed, err
+	}
+	return http.StatusCreated, nil
+}
+
+func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status int, err error) {
+	hdr := r.Header.Get("Destination")
+	if hdr == "" {
+		return http.StatusBadRequest, errInvalidDestination
+	}
+	u, err := url.Parse(hdr)
+	if err != nil {
+		return http.StatusBadRequest, errInvalidDestination
+	}
+	if u.Host != "" && u.Host != r.Host {
+		return http.StatusBadGateway, errInvalidDestination
+	}
+
+	src, status, err := h.stripPrefix(r.URL.Path)
+	if err != nil {
+		return status, err
+	}
+
+	dst, status, err := h.stripPrefix(u.Path)
+	if err != nil {
+		return status, err
+	}
+
+	if dst == "" {
+		return http.StatusBadGateway, errInvalidDestination
+	}
+	if dst == src {
+		return http.StatusForbidden, errDestinationEqualsSource
+	}
+
+	ctx := r.Context()
+
+	if r.Method == "COPY" {
+		// Section 7.5.1 says that a COPY only needs to lock the destination,
+		// not both destination and source. Strictly speaking, this is racy,
+		// even though a COPY doesn't modify the source, if a concurrent
+		// operation modifies the source. However, the litmus test explicitly
+		// checks that COPYing a locked-by-another source is OK.
+		release, status, err := h.confirmLocks(r, "", dst)
+		if err != nil {
+			return status, err
+		}
+		defer release()
+
+		// Section 9.8.3 says that "The COPY method on a collection without a Depth
+		// header must act as if a Depth header with value "infinity" was included".
+		depth := infiniteDepth
+		if hdr := r.Header.Get("Depth"); hdr != "" {
+			depth = parseDepth(hdr)
+			if depth != 0 && depth != infiniteDepth {
+				// Section 9.8.3 says that "A client may submit a Depth header on a
+				// COPY on a collection with a value of "0" or "infinity"."
+				return http.StatusBadRequest, errInvalidDepth
+			}
+		}
+		return copyFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") != "F", depth, 0)
+	}
+
+	release, status, err := h.confirmLocks(r, src, dst)
+	if err != nil {
+		return status, err
+	}
+	defer release()
+
+	// Section 9.9.2 says that "The MOVE method on a collection must act as if
+	// a "Depth: infinity" header was used on it. A client must not submit a
+	// Depth header on a MOVE on a collection with any value but "infinity"."
+	if hdr := r.Header.Get("Depth"); hdr != "" {
+		if parseDepth(hdr) != infiniteDepth {
+			return http.StatusBadRequest, errInvalidDepth
+		}
+	}
+	return moveFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") == "T")
+}
+
+func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus int, retErr error) {
+	duration, err := parseTimeout(r.Header.Get("Timeout"))
+	if err != nil {
+		return http.StatusBadRequest, err
+	}
+	li, status, err := readLockInfo(r.Body)
+	if err != nil {
+		return status, err
+	}
+
+	ctx := r.Context()
+	token, ld, now, created := "", LockDetails{}, time.Now(), false
+	if li == (lockInfo{}) {
+		// An empty lockInfo means to refresh the lock.
+		ih, ok := parseIfHeader(r.Header.Get("If"))
+		if !ok {
+			return http.StatusBadRequest, errInvalidIfHeader
+		}
+		if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 {
+			token = ih.lists[0].conditions[0].Token
+		}
+		if token == "" {
+			return http.StatusBadRequest, errInvalidLockToken
+		}
+		ld, err = h.LockSystem.Refresh(now, token, duration)
+		if err != nil {
+			if err == ErrNoSuchLock {
+				return http.StatusPreconditionFailed, err
+			}
+			return http.StatusInternalServerError, err
+		}
+
+	} else {
+		// Section 9.10.3 says that "If no Depth header is submitted on a LOCK request,
+		// then the request MUST act as if a "Depth:infinity" had been submitted."
+		depth := infiniteDepth
+		if hdr := r.Header.Get("Depth"); hdr != "" {
+			depth = parseDepth(hdr)
+			if depth != 0 && depth != infiniteDepth {
+				// Section 9.10.3 says that "Values other than 0 or infinity must not be
+				// used with the Depth header on a LOCK method".
+				return http.StatusBadRequest, errInvalidDepth
+			}
+		}
+		reqPath, status, err := h.stripPrefix(r.URL.Path)
+		if err != nil {
+			return status, err
+		}
+		ld = LockDetails{
+			Root:      reqPath,
+			Duration:  duration,
+			OwnerXML:  li.Owner.InnerXML,
+			ZeroDepth: depth == 0,
+		}
+		token, err = h.LockSystem.Create(now, ld)
+		if err != nil {
+			if err == ErrLocked {
+				return StatusLocked, err
+			}
+			return http.StatusInternalServerError, err
+		}
+		defer func() {
+			if retErr != nil {
+				h.LockSystem.Unlock(now, token)
+			}
+		}()
+
+		// Create the resource if it didn't previously exist.
+		if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
+			f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+			if err != nil {
+				// TODO: detect missing intermediate dirs and return http.StatusConflict?
+				return http.StatusInternalServerError, err
+			}
+			f.Close()
+			created = true
+		}
+
+		// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
+		// Lock-Token value is a Coded-URL. We add angle brackets.
+		w.Header().Set("Lock-Token", "<"+token+">")
+	}
+
+	w.Header().Set("Content-Type", "application/xml; charset=utf-8")
+	if created {
+		// This is "w.WriteHeader(http.StatusCreated)" and not "return
+		// http.StatusCreated, nil" because we write our own (XML) response to w
+		// and Handler.ServeHTTP would otherwise write "Created".
+		w.WriteHeader(http.StatusCreated)
+	}
+	writeLockInfo(w, token, ld)
+	return 0, nil
+}
+
+func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request) (status int, err error) {
+	// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
+	// Lock-Token value is a Coded-URL. We strip its angle brackets.
+	t := r.Header.Get("Lock-Token")
+	if len(t) < 2 || t[0] != '<' || t[len(t)-1] != '>' {
+		return http.StatusBadRequest, errInvalidLockToken
+	}
+	t = t[1 : len(t)-1]
+
+	switch err = h.LockSystem.Unlock(time.Now(), t); err {
+	case nil:
+		return http.StatusNoContent, err
+	case ErrForbidden:
+		return http.StatusForbidden, err
+	case ErrLocked:
+		return StatusLocked, err
+	case ErrNoSuchLock:
+		return http.StatusConflict, err
+	default:
+		return http.StatusInternalServerError, err
+	}
+}
+
+func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status int, err error) {
+	reqPath, status, err := h.stripPrefix(r.URL.Path)
+	if err != nil {
+		return status, err
+	}
+	ctx := r.Context()
+	fi, err := h.FileSystem.Stat(ctx, reqPath)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return http.StatusNotFound, err
+		}
+		return http.StatusMethodNotAllowed, err
+	}
+	depth := infiniteDepth
+	if hdr := r.Header.Get("Depth"); hdr != "" {
+		depth = parseDepth(hdr)
+		if depth == invalidDepth {
+			return http.StatusBadRequest, errInvalidDepth
+		}
+	}
+	pf, status, err := readPropfind(r.Body)
+	if err != nil {
+		return status, err
+	}
+
+	mw := multistatusWriter{w: w}
+
+	walkFn := func(reqPath string, info os.FileInfo, err error) error {
+		if err != nil {
+			return nil
+		}
+
+		//Arozos custom properties
+		/*
+			if len(reqPath) > 1 && filepath.Base(reqPath)[:1] == "." {
+				//Hidden file. Do not show
+				return nil
+			}
+		*/
+
+		var pstats []Propstat
+		if pf.Propname != nil {
+			pnames, err := propnames(ctx, h.FileSystem, h.LockSystem, reqPath)
+			if err != nil {
+				return nil
+			}
+			pstat := Propstat{Status: http.StatusOK}
+			for _, xmlname := range pnames {
+				pstat.Props = append(pstat.Props, Property{XMLName: xmlname})
+			}
+			pstats = append(pstats, pstat)
+		} else if pf.Allprop != nil {
+			pstats, err = allprop(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
+		} else {
+			pstats, err = props(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
+		}
+		if err != nil {
+			return nil
+		}
+		href := path.Join(h.Prefix, reqPath)
+		if href != "/" && info.IsDir() {
+			href += "/"
+		}
+		return mw.write(makePropstatResponse(href, pstats))
+	}
+
+	if h.RequestEventListener != nil {
+		h.RequestEventListener(reqPath)
+	}
+	walkErr := walkFS(ctx, h.FileSystem, depth, reqPath, fi, walkFn)
+	closeErr := mw.close()
+	if walkErr != nil {
+		return http.StatusInternalServerError, walkErr
+	}
+	if closeErr != nil {
+		return http.StatusInternalServerError, closeErr
+	}
+	return 0, nil
+}
+
+func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (status int, err error) {
+	reqPath, status, err := h.stripPrefix(r.URL.Path)
+	if err != nil {
+		return status, err
+	}
+	release, status, err := h.confirmLocks(r, reqPath, "")
+	if err != nil {
+		return status, err
+	}
+	defer release()
+
+	ctx := r.Context()
+
+	if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
+		if os.IsNotExist(err) {
+			return http.StatusNotFound, err
+		}
+		return http.StatusMethodNotAllowed, err
+	}
+	patches, status, err := readProppatch(r.Body)
+	if err != nil {
+		return status, err
+	}
+	pstats, err := patch(ctx, h.FileSystem, h.LockSystem, reqPath, patches)
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+	mw := multistatusWriter{w: w}
+	writeErr := mw.write(makePropstatResponse(r.URL.Path, pstats))
+	closeErr := mw.close()
+	if writeErr != nil {
+		return http.StatusInternalServerError, writeErr
+	}
+	if closeErr != nil {
+		return http.StatusInternalServerError, closeErr
+	}
+	return 0, nil
+}
+
+func makePropstatResponse(href string, pstats []Propstat) *response {
+	resp := response{
+		Href:     []string{(&url.URL{Path: href}).EscapedPath()},
+		Propstat: make([]propstat, 0, len(pstats)),
+	}
+	for _, p := range pstats {
+		var xmlErr *xmlError
+		if p.XMLError != "" {
+			xmlErr = &xmlError{InnerXML: []byte(p.XMLError)}
+		}
+		resp.Propstat = append(resp.Propstat, propstat{
+			Status:              fmt.Sprintf("HTTP/1.1 %d %s", p.Status, StatusText(p.Status)),
+			Prop:                p.Props,
+			ResponseDescription: p.ResponseDescription,
+			Error:               xmlErr,
+		})
+	}
+	return &resp
+}
+
+const (
+	infiniteDepth = -1
+	invalidDepth  = -2
+)
+
+// parseDepth maps the strings "0", "1" and "infinity" to 0, 1 and
+// infiniteDepth. Parsing any other string returns invalidDepth.
+//
+// Different WebDAV methods have further constraints on valid depths:
+//   - PROPFIND has no further restrictions, as per section 9.1.
+//   - COPY accepts only "0" or "infinity", as per section 9.8.3.
+//   - MOVE accepts only "infinity", as per section 9.9.2.
+//   - LOCK accepts only "0" or "infinity", as per section 9.10.3.
+//
+// These constraints are enforced by the handleXxx methods.
+func parseDepth(s string) int {
+	switch s {
+	case "0":
+		return 0
+	case "1":
+		return 1
+	case "infinity":
+		return infiniteDepth
+	}
+	return invalidDepth
+}
+
+// http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11
+const (
+	StatusMulti               = 207
+	StatusUnprocessableEntity = 422
+	StatusLocked              = 423
+	StatusFailedDependency    = 424
+	StatusInsufficientStorage = 507
+)
+
+func StatusText(code int) string {
+	switch code {
+	case StatusMulti:
+		return "Multi-Status"
+	case StatusUnprocessableEntity:
+		return "Unprocessable Entity"
+	case StatusLocked:
+		return "Locked"
+	case StatusFailedDependency:
+		return "Failed Dependency"
+	case StatusInsufficientStorage:
+		return "Insufficient Storage"
+	}
+	return http.StatusText(code)
+}
+
+var (
+	errDestinationEqualsSource = errors.New("webdav: destination equals source")
+	errDirectoryNotEmpty       = errors.New("webdav: directory not empty")
+	errInvalidDepth            = errors.New("webdav: invalid depth")
+	errInvalidDestination      = errors.New("webdav: invalid destination")
+	errInvalidIfHeader         = errors.New("webdav: invalid If header")
+	errInvalidLockInfo         = errors.New("webdav: invalid lock info")
+	errInvalidLockToken        = errors.New("webdav: invalid lock token")
+	errInvalidPropfind         = errors.New("webdav: invalid propfind")
+	errInvalidProppatch        = errors.New("webdav: invalid proppatch")
+	errInvalidResponse         = errors.New("webdav: invalid response")
+	errInvalidTimeout          = errors.New("webdav: invalid timeout")
+	errNoFileSystem            = errors.New("webdav: no file system")
+	errNoLockSystem            = errors.New("webdav: no lock system")
+	errNotADirectory           = errors.New("webdav: not a directory")
+	errPrefixMismatch          = errors.New("webdav: prefix mismatch")
+	errRecursionTooDeep        = errors.New("webdav: recursion too deep")
+	errUnsupportedLockInfo     = errors.New("webdav: unsupported lock info")
+	errUnsupportedMethod       = errors.New("webdav: unsupported method")
+)

+ 13 - 11
mod/storage/webdav/webdav.go

@@ -49,7 +49,7 @@ type WindowClientInfo struct {
 	ClientIP                string
 }
 
-//NewServer create a new WebDAV server object required by arozos
+// NewServer create a new WebDAV server object required by arozos
 func NewServer(hostname string, prefix string, tmpdir string, tlsMode bool, userHandler *user.UserHandler) *Server {
 	//Generate a default handler
 	os.MkdirAll(filepath.Join(tmpdir, "webdav"), 0777)
@@ -87,7 +87,7 @@ func (s *Server) HandleClearAllPending(w http.ResponseWriter, r *http.Request) {
 	sendOK(w)
 }
 
-//Handle allow and remove permission of a windows WebDAV Client
+// Handle allow and remove permission of a windows WebDAV Client
 func (s *Server) HandlePermissionEdit(w http.ResponseWriter, r *http.Request) {
 	opr, err := utils.PostPara(r, "opr")
 	if err != nil {
@@ -222,11 +222,13 @@ func (s *Server) HandleRequest(w http.ResponseWriter, r *http.Request) {
 	}
 
 	//Windows File Explorer. Handle with special case
-	if r.Header["User-Agent"] != nil && strings.Contains(r.Header["User-Agent"][0], "Microsoft-WebDAV-MiniRedir") && r.TLS == nil {
-		log.Println("Windows File Explorer Connection. Routing using alternative handler")
-		s.HandleWindowClientAccess(w, r, reqRoot)
-		return
-	}
+	/*
+		if r.Header["User-Agent"] != nil && strings.Contains(r.Header["User-Agent"][0], "Microsoft-WebDAV-MiniRedir") && r.TLS == nil {
+			log.Println("Windows File Explorer Connection. Routing using alternative handler")
+			s.HandleWindowClientAccess(w, r, reqRoot)
+			return
+		}
+	*/
 
 	username, password, ok := r.BasicAuth()
 	if !ok {
@@ -289,11 +291,11 @@ func (s *Server) HandleRequest(w http.ResponseWriter, r *http.Request) {
 }
 
 /*
-	Serve ReadOnly WebDAV Server
+Serve ReadOnly WebDAV Server
 
-	This section exists because Windows WebDAV Services require a
-	success connection in order to store the cookie. If nothing is served,
-	File Explorer will not cache the cookie in its cache
+This section exists because Windows WebDAV Services require a
+success connection in order to store the cookie. If nothing is served,
+File Explorer will not cache the cookie in its cache
 */
 func (s *Server) serveReadOnlyWebDav(w http.ResponseWriter, r *http.Request) {
 	if r.Method == "PUT" || r.Method == "POST" || r.Method == "MKCOL" ||