| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 | // 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 webdavimport (	"context"	"errors"	"fmt"	"io"	"net/http"	"net/http/httptest"	"net/url"	"os"	"reflect"	"regexp"	"sort"	"strings"	"testing")// TODO: add tests to check XML responses with the expected prefix pathfunc TestPrefix(t *testing.T) {	const dst, blah = "Destination", "blah blah blah"	// createLockBody comes from the example in Section 9.10.7.	const createLockBody = `<?xml version="1.0" encoding="utf-8" ?>		<D:lockinfo xmlns:D='DAV:'>			<D:lockscope><D:exclusive/></D:lockscope>			<D:locktype><D:write/></D:locktype>			<D:owner>				<D:href>http://example.org/~ejw/contact.html</D:href>			</D:owner>		</D:lockinfo>	`	do := func(method, urlStr string, body string, wantStatusCode int, headers ...string) (http.Header, error) {		var bodyReader io.Reader		if body != "" {			bodyReader = strings.NewReader(body)		}		req, err := http.NewRequest(method, urlStr, bodyReader)		if err != nil {			return nil, err		}		for len(headers) >= 2 {			req.Header.Add(headers[0], headers[1])			headers = headers[2:]		}		res, err := http.DefaultTransport.RoundTrip(req)		if err != nil {			return nil, err		}		defer res.Body.Close()		if res.StatusCode != wantStatusCode {			return nil, fmt.Errorf("got status code %d, want %d", res.StatusCode, wantStatusCode)		}		return res.Header, nil	}	prefixes := []string{		"/",		"/a/",		"/a/b/",		"/a/b/c/",	}	ctx := context.Background()	for _, prefix := range prefixes {		fs := NewMemFS()		h := &Handler{			FileSystem: fs,			LockSystem: NewMemLS(),		}		mux := http.NewServeMux()		if prefix != "/" {			h.Prefix = prefix		}		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		//	LOCK  /a/b/e/g		//	PUT   /a/b/e/g		// which should yield the (possibly stripped) filenames /a/b/c,		// /a/b/e/f and /a/b/e/g, 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", "", 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", "", 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", 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", "", 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", "", 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", "", 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		}		var lockToken string		wantG := map[string]int{			"/":       http.StatusCreated,			"/a/":     http.StatusCreated,			"/a/b/":   http.StatusCreated,			"/a/b/c/": http.StatusNotFound,		}[prefix]		if h, err := do("LOCK", srv.URL+"/a/b/e/g", createLockBody, wantG); err != nil {			t.Errorf("prefix=%-9q LOCK /a/b/e/g: %v", prefix, err)			continue		} else {			lockToken = h.Get("Lock-Token")		}		ifHeader := fmt.Sprintf("<%s/a/b/e/g> (%s)", srv.URL, lockToken)		wantH := map[string]int{			"/":       http.StatusCreated,			"/a/":     http.StatusCreated,			"/a/b/":   http.StatusCreated,			"/a/b/c/": http.StatusNotFound,		}[prefix]		if _, err := do("PUT", srv.URL+"/a/b/e/g", blah, wantH, "If", ifHeader); err != nil {			t.Errorf("prefix=%-9q PUT /a/b/e/g: %v", prefix, err)			continue		}		got, err := find(ctx, nil, fs, "/")		if err != nil {			t.Errorf("prefix=%-9q find: %v", prefix, err)			continue		}		sort.Strings(got)		want := map[string][]string{			"/":       {"/", "/a", "/a/b", "/a/b/c", "/a/b/e", "/a/b/e/f", "/a/b/e/g"},			"/a/":     {"/", "/b", "/b/c", "/b/e", "/b/e/f", "/b/e/g"},			"/a/b/":   {"/", "/c", "/e", "/e/f", "/e/g"},			"/a/b/c/": {"/"},		}[prefix]		if !reflect.DeepEqual(got, want) {			t.Errorf("prefix=%-9q find:\ngot  %v\nwant %v", prefix, got, want)			continue		}	}}func TestEscapeXML(t *testing.T) {	// These test cases aren't exhaustive, and there is more than one way to	// escape e.g. a quot (as """ or """) or an apos. We presume that	// the encoding/xml package tests xml.EscapeText more thoroughly. This test	// here is just a sanity check for this package's escapeXML function, and	// its attempt to provide a fast path (and avoid a bytes.Buffer allocation)	// when escaping filenames is obviously a no-op.	testCases := map[string]string{		"":              "",		" ":             " ",		"&":             "&",		"*":             "*",		"+":             "+",		",":             ",",		"-":             "-",		".":             ".",		"/":             "/",		"0":             "0",		"9":             "9",		":":             ":",		"<":             "<",		">":             ">",		"A":             "A",		"_":             "_",		"a":             "a",		"~":             "~",		"\u0201":        "\u0201",		"&":         "&amp;",		"foo&<b/ar>baz": "foo&<b/ar>baz",	}	for in, want := range testCases {		if got := escapeXML(in); got != want {			t.Errorf("in=%q: got %q, want %q", in, got, want)		}	}}func TestFilenameEscape(t *testing.T) {	hrefRe := regexp.MustCompile(`<D:href>([^<]*)</D:href>`)	displayNameRe := regexp.MustCompile(`<D:displayname>([^<]*)</D:displayname>`)	do := func(method, urlStr string) (string, string, error) {		req, err := http.NewRequest(method, urlStr, nil)		if err != nil {			return "", "", err		}		res, err := http.DefaultClient.Do(req)		if err != nil {			return "", "", err		}		defer res.Body.Close()		b, err := io.ReadAll(res.Body)		if err != nil {			return "", "", err		}		hrefMatch := hrefRe.FindStringSubmatch(string(b))		if len(hrefMatch) != 2 {			return "", "", errors.New("D:href not found")		}		displayNameMatch := displayNameRe.FindStringSubmatch(string(b))		if len(displayNameMatch) != 2 {			return "", "", errors.New("D:displayname not found")		}		return hrefMatch[1], displayNameMatch[1], nil	}	testCases := []struct {		name, wantHref, wantDisplayName string	}{{		name:            `/foo%bar`,		wantHref:        `/foo%25bar`,		wantDisplayName: `foo%bar`,	}, {		name:            `/こんにちわ世界`,		wantHref:        `/%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%82%8F%E4%B8%96%E7%95%8C`,		wantDisplayName: `こんにちわ世界`,	}, {		name:            `/Program Files/`,		wantHref:        `/Program%20Files/`,		wantDisplayName: `Program Files`,	}, {		name:            `/go+lang`,		wantHref:        `/go+lang`,		wantDisplayName: `go+lang`,	}, {		name:            `/go&lang`,		wantHref:        `/go&lang`,		wantDisplayName: `go&lang`,	}, {		name:            `/go<lang`,		wantHref:        `/go%3Clang`,		wantDisplayName: `go<lang`,	}, {		name:            `/`,		wantHref:        `/`,		wantDisplayName: ``,	}}	ctx := context.Background()	fs := NewMemFS()	for _, tc := range testCases {		if tc.name != "/" {			if strings.HasSuffix(tc.name, "/") {				if err := fs.Mkdir(ctx, tc.name, 0755); err != nil {					t.Fatalf("name=%q: Mkdir: %v", tc.name, err)				}			} else {				f, err := fs.OpenFile(ctx, tc.name, os.O_CREATE, 0644)				if err != nil {					t.Fatalf("name=%q: OpenFile: %v", tc.name, err)				}				f.Close()			}		}	}	srv := httptest.NewServer(&Handler{		FileSystem: fs,		LockSystem: NewMemLS(),	})	defer srv.Close()	u, err := url.Parse(srv.URL)	if err != nil {		t.Fatal(err)	}	for _, tc := range testCases {		u.Path = tc.name		gotHref, gotDisplayName, err := do("PROPFIND", u.String())		if err != nil {			t.Errorf("name=%q: PROPFIND: %v", tc.name, err)			continue		}		if gotHref != tc.wantHref {			t.Errorf("name=%q: got href %q, want %q", tc.name, gotHref, tc.wantHref)		}		if gotDisplayName != tc.wantDisplayName {			t.Errorf("name=%q: got dispayname %q, want %q", tc.name, gotDisplayName, tc.wantDisplayName)		}	}}
 |