Jelajahi Sumber

webdav: add StripPrefix.

Change-Id: I38ac4a507cb79d4295c58f2d8c891e5bdcf0c1e4
Reviewed-on: https://go-review.googlesource.com/10379
Reviewed-by: Robert Stepanek <robert.stepanek@gmail.com>
Reviewed-by: Nigel Tao <nigeltao@golang.org>
Nigel Tao 10 tahun lalu
induk
melakukan
7ca853dc26
2 mengubah file dengan 184 tambahan dan 3 penghapusan
  1. 23 3
      webdav/webdav.go
  2. 161 0
      webdav/webdav_test.go

+ 23 - 3
webdav/webdav.go

@@ -301,9 +301,6 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
 	if u.Host != r.Host {
 		return http.StatusBadGateway, errInvalidDestination
 	}
-	// TODO: do we need a webdav.StripPrefix HTTP handler that's like the
-	// standard library's http.StripPrefix handler, but also strips the
-	// prefix in the Destination header?
 
 	dst, src := u.Path, r.URL.Path
 	if dst == "" {
@@ -626,6 +623,29 @@ func parseDepth(s string) int {
 	return invalidDepth
 }
 
+// StripPrefix is like http.StripPrefix but it also strips the prefix from any
+// Destination headers, so that COPY and MOVE requests also see stripped paths.
+func StripPrefix(prefix string, h http.Handler) http.Handler {
+	if prefix == "" {
+		return h
+	}
+	h = http.StripPrefix(prefix, h)
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		dsts := r.Header["Destination"]
+		for i, dst := range dsts {
+			u, err := url.Parse(dst)
+			if err != nil {
+				continue
+			}
+			if p := strings.TrimPrefix(u.Path, prefix); len(p) < len(u.Path) {
+				u.Path = p
+				dsts[i] = u.String()
+			}
+		}
+		h.ServeHTTP(w, r)
+	})
+}
+
 // http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11
 const (
 	StatusMulti               = 207

+ 161 - 0
webdav/webdav_test.go

@@ -0,0 +1,161 @@
+// Copyright 2015 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
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+	"net/http/httptest"
+	"reflect"
+	"sort"
+	"strings"
+	"testing"
+)
+
+// TestStripPrefix tests the StripPrefix function. We can't test the
+// StripPrefix function with the litmus test, even though all of the litmus
+// test paths start with "/litmus/", because one of the first things that the
+// litmus test does is "MKCOL /litmus/". That request succeeds without a
+// StripPrefix, but fails with a StripPrefix because you cannot MKCOL the root
+// directory of a FileSystem.
+func TestStripPrefix(t *testing.T) {
+	const dst, blah = "Destination", "blah blah blah"
+
+	do := func(method, urlStr string, body io.Reader, wantStatusCode int, headers ...string) error {
+		req, err := http.NewRequest(method, urlStr, body)
+		if err != nil {
+			return err
+		}
+		for len(headers) >= 2 {
+			req.Header.Add(headers[0], headers[1])
+			headers = headers[2:]
+		}
+		res, err := http.DefaultClient.Do(req)
+		if err != nil {
+			return err
+		}
+		defer res.Body.Close()
+		if res.StatusCode != wantStatusCode {
+			return fmt.Errorf("got status code %d, want %d", res.StatusCode, wantStatusCode)
+		}
+		return nil
+	}
+
+	prefixes := []string{
+		"/",
+		"/a/",
+		"/a/b/",
+		"/a/b/c/",
+	}
+	for _, prefix := range prefixes {
+		fs := NewMemFS()
+		h := http.Handler(&Handler{
+			FileSystem: fs,
+			LockSystem: NewMemLS(),
+		})
+		mux := http.NewServeMux()
+		if prefix != "/" {
+			// Note that this is webdav.StripPrefix, not http.StripPrefix.
+			h = StripPrefix(prefix, h)
+		}
+		mux.Handle(prefix, h)
+		srv := httptest.NewServer(mux)
+		defer srv.Close()
+
+		// The script is:
+		//	MKCOL /a
+		//	MKCOL /a/b
+		//	PUT   /a/b/c
+		//	COPY  /a/b/c /a/b/d
+		//	MKCOL /a/b/e
+		//	MOVE  /a/b/d /a/b/e/f
+		// which should yield the (possibly stripped) filenames /a/b/c and
+		// /a/b/e/f, plus their parent directories.
+
+		wantA := map[string]int{
+			"/":       http.StatusCreated,
+			"/a/":     http.StatusMovedPermanently,
+			"/a/b/":   http.StatusNotFound,
+			"/a/b/c/": http.StatusNotFound,
+		}[prefix]
+		if err := do("MKCOL", srv.URL+"/a", nil, wantA); err != nil {
+			t.Errorf("prefix=%-9q MKCOL /a: %v", prefix, err)
+			continue
+		}
+
+		wantB := map[string]int{
+			"/":       http.StatusCreated,
+			"/a/":     http.StatusCreated,
+			"/a/b/":   http.StatusMovedPermanently,
+			"/a/b/c/": http.StatusNotFound,
+		}[prefix]
+		if err := do("MKCOL", srv.URL+"/a/b", nil, wantB); err != nil {
+			t.Errorf("prefix=%-9q MKCOL /a/b: %v", prefix, err)
+			continue
+		}
+
+		wantC := map[string]int{
+			"/":       http.StatusCreated,
+			"/a/":     http.StatusCreated,
+			"/a/b/":   http.StatusCreated,
+			"/a/b/c/": http.StatusMovedPermanently,
+		}[prefix]
+		if err := do("PUT", srv.URL+"/a/b/c", strings.NewReader(blah), wantC); err != nil {
+			t.Errorf("prefix=%-9q PUT /a/b/c: %v", prefix, err)
+			continue
+		}
+
+		wantD := map[string]int{
+			"/":       http.StatusCreated,
+			"/a/":     http.StatusCreated,
+			"/a/b/":   http.StatusCreated,
+			"/a/b/c/": http.StatusMovedPermanently,
+		}[prefix]
+		if err := do("COPY", srv.URL+"/a/b/c", nil, wantD, dst, srv.URL+"/a/b/d"); err != nil {
+			t.Errorf("prefix=%-9q COPY /a/b/c /a/b/d: %v", prefix, err)
+			continue
+		}
+
+		wantE := map[string]int{
+			"/":       http.StatusCreated,
+			"/a/":     http.StatusCreated,
+			"/a/b/":   http.StatusCreated,
+			"/a/b/c/": http.StatusNotFound,
+		}[prefix]
+		if err := do("MKCOL", srv.URL+"/a/b/e", nil, wantE); err != nil {
+			t.Errorf("prefix=%-9q MKCOL /a/b/e: %v", prefix, err)
+			continue
+		}
+
+		wantF := map[string]int{
+			"/":       http.StatusCreated,
+			"/a/":     http.StatusCreated,
+			"/a/b/":   http.StatusCreated,
+			"/a/b/c/": http.StatusNotFound,
+		}[prefix]
+		if err := do("MOVE", srv.URL+"/a/b/d", nil, wantF, dst, srv.URL+"/a/b/e/f"); err != nil {
+			t.Errorf("prefix=%-9q MOVE /a/b/d /a/b/e/f: %v", prefix, err)
+			continue
+		}
+
+		got, err := find(nil, fs, "/")
+		if err != nil {
+			t.Errorf("prefix=%-9q find: %v", prefix, err)
+			continue
+		}
+		sort.Strings(got)
+		want := map[string][]string{
+			"/":       []string{"/", "/a", "/a/b", "/a/b/c", "/a/b/e", "/a/b/e/f"},
+			"/a/":     []string{"/", "/b", "/b/c", "/b/e", "/b/e/f"},
+			"/a/b/":   []string{"/", "/c", "/e", "/e/f"},
+			"/a/b/c/": []string{"/"},
+		}[prefix]
+		if !reflect.DeepEqual(got, want) {
+			t.Errorf("prefix=%-9q find:\ngot  %v\nwant %v", prefix, got, want)
+			continue
+		}
+	}
+}