gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/net/webdav/webdav_test.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package webdav 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net/url" 14 "os" 15 "reflect" 16 "regexp" 17 "sort" 18 "strings" 19 "testing" 20 21 "gitee.com/ks-custle/core-gm/gmhttp/httptest" 22 23 http "gitee.com/ks-custle/core-gm/gmhttp" 24 ) 25 26 // TODO: add tests to check XML responses with the expected prefix path 27 func TestPrefix(t *testing.T) { 28 const dst, blah = "Destination", "blah blah blah" 29 30 // createLockBody comes from the example in Section 9.10.7. 31 const createLockBody = `<?xml version="1.0" encoding="utf-8" ?> 32 <D:lockinfo xmlns:D='DAV:'> 33 <D:lockscope><D:exclusive/></D:lockscope> 34 <D:locktype><D:write/></D:locktype> 35 <D:owner> 36 <D:href>http://example.org/~ejw/contact.html</D:href> 37 </D:owner> 38 </D:lockinfo> 39 ` 40 41 do := func(method, urlStr string, body string, wantStatusCode int, headers ...string) (http.Header, error) { 42 var bodyReader io.Reader 43 if body != "" { 44 bodyReader = strings.NewReader(body) 45 } 46 req, err := http.NewRequest(method, urlStr, bodyReader) 47 if err != nil { 48 return nil, err 49 } 50 for len(headers) >= 2 { 51 req.Header.Add(headers[0], headers[1]) 52 headers = headers[2:] 53 } 54 res, err := http.DefaultTransport.RoundTrip(req) 55 if err != nil { 56 return nil, err 57 } 58 defer res.Body.Close() 59 if res.StatusCode != wantStatusCode { 60 return nil, fmt.Errorf("got status code %d, want %d", res.StatusCode, wantStatusCode) 61 } 62 return res.Header, nil 63 } 64 65 prefixes := []string{ 66 "/", 67 "/a/", 68 "/a/b/", 69 "/a/b/c/", 70 } 71 ctx := context.Background() 72 for _, prefix := range prefixes { 73 fs := NewMemFS() 74 h := &Handler{ 75 FileSystem: fs, 76 LockSystem: NewMemLS(), 77 } 78 mux := http.NewServeMux() 79 if prefix != "/" { 80 h.Prefix = prefix 81 } 82 mux.Handle(prefix, h) 83 srv := httptest.NewServer(mux) 84 defer srv.Close() 85 86 // The script is: 87 // MKCOL /a 88 // MKCOL /a/b 89 // PUT /a/b/c 90 // COPY /a/b/c /a/b/d 91 // MKCOL /a/b/e 92 // MOVE /a/b/d /a/b/e/f 93 // LOCK /a/b/e/g 94 // PUT /a/b/e/g 95 // which should yield the (possibly stripped) filenames /a/b/c, 96 // /a/b/e/f and /a/b/e/g, plus their parent directories. 97 98 wantA := map[string]int{ 99 "/": http.StatusCreated, 100 "/a/": http.StatusMovedPermanently, 101 "/a/b/": http.StatusNotFound, 102 "/a/b/c/": http.StatusNotFound, 103 }[prefix] 104 if _, err := do("MKCOL", srv.URL+"/a", "", wantA); err != nil { 105 t.Errorf("prefix=%-9q MKCOL /a: %v", prefix, err) 106 continue 107 } 108 109 wantB := map[string]int{ 110 "/": http.StatusCreated, 111 "/a/": http.StatusCreated, 112 "/a/b/": http.StatusMovedPermanently, 113 "/a/b/c/": http.StatusNotFound, 114 }[prefix] 115 if _, err := do("MKCOL", srv.URL+"/a/b", "", wantB); err != nil { 116 t.Errorf("prefix=%-9q MKCOL /a/b: %v", prefix, err) 117 continue 118 } 119 120 wantC := map[string]int{ 121 "/": http.StatusCreated, 122 "/a/": http.StatusCreated, 123 "/a/b/": http.StatusCreated, 124 "/a/b/c/": http.StatusMovedPermanently, 125 }[prefix] 126 if _, err := do("PUT", srv.URL+"/a/b/c", blah, wantC); err != nil { 127 t.Errorf("prefix=%-9q PUT /a/b/c: %v", prefix, err) 128 continue 129 } 130 131 wantD := map[string]int{ 132 "/": http.StatusCreated, 133 "/a/": http.StatusCreated, 134 "/a/b/": http.StatusCreated, 135 "/a/b/c/": http.StatusMovedPermanently, 136 }[prefix] 137 if _, err := do("COPY", srv.URL+"/a/b/c", "", wantD, dst, srv.URL+"/a/b/d"); err != nil { 138 t.Errorf("prefix=%-9q COPY /a/b/c /a/b/d: %v", prefix, err) 139 continue 140 } 141 142 wantE := map[string]int{ 143 "/": http.StatusCreated, 144 "/a/": http.StatusCreated, 145 "/a/b/": http.StatusCreated, 146 "/a/b/c/": http.StatusNotFound, 147 }[prefix] 148 if _, err := do("MKCOL", srv.URL+"/a/b/e", "", wantE); err != nil { 149 t.Errorf("prefix=%-9q MKCOL /a/b/e: %v", prefix, err) 150 continue 151 } 152 153 wantF := map[string]int{ 154 "/": http.StatusCreated, 155 "/a/": http.StatusCreated, 156 "/a/b/": http.StatusCreated, 157 "/a/b/c/": http.StatusNotFound, 158 }[prefix] 159 if _, err := do("MOVE", srv.URL+"/a/b/d", "", wantF, dst, srv.URL+"/a/b/e/f"); err != nil { 160 t.Errorf("prefix=%-9q MOVE /a/b/d /a/b/e/f: %v", prefix, err) 161 continue 162 } 163 164 var lockToken string 165 wantG := map[string]int{ 166 "/": http.StatusCreated, 167 "/a/": http.StatusCreated, 168 "/a/b/": http.StatusCreated, 169 "/a/b/c/": http.StatusNotFound, 170 }[prefix] 171 if h, err := do("LOCK", srv.URL+"/a/b/e/g", createLockBody, wantG); err != nil { 172 t.Errorf("prefix=%-9q LOCK /a/b/e/g: %v", prefix, err) 173 continue 174 } else { 175 lockToken = h.Get("Lock-Token") 176 } 177 178 ifHeader := fmt.Sprintf("<%s/a/b/e/g> (%s)", srv.URL, lockToken) 179 wantH := map[string]int{ 180 "/": http.StatusCreated, 181 "/a/": http.StatusCreated, 182 "/a/b/": http.StatusCreated, 183 "/a/b/c/": http.StatusNotFound, 184 }[prefix] 185 if _, err := do("PUT", srv.URL+"/a/b/e/g", blah, wantH, "If", ifHeader); err != nil { 186 t.Errorf("prefix=%-9q PUT /a/b/e/g: %v", prefix, err) 187 continue 188 } 189 190 got, err := find(ctx, nil, fs, "/") 191 if err != nil { 192 t.Errorf("prefix=%-9q find: %v", prefix, err) 193 continue 194 } 195 sort.Strings(got) 196 want := map[string][]string{ 197 "/": {"/", "/a", "/a/b", "/a/b/c", "/a/b/e", "/a/b/e/f", "/a/b/e/g"}, 198 "/a/": {"/", "/b", "/b/c", "/b/e", "/b/e/f", "/b/e/g"}, 199 "/a/b/": {"/", "/c", "/e", "/e/f", "/e/g"}, 200 "/a/b/c/": {"/"}, 201 }[prefix] 202 if !reflect.DeepEqual(got, want) { 203 t.Errorf("prefix=%-9q find:\ngot %v\nwant %v", prefix, got, want) 204 continue 205 } 206 } 207 } 208 209 func TestEscapeXML(t *testing.T) { 210 // These test cases aren't exhaustive, and there is more than one way to 211 // escape e.g. a quot (as """ or """) or an apos. We presume that 212 // the encoding/xml package tests xml.EscapeText more thoroughly. This test 213 // here is just a sanity check for this package's escapeXML function, and 214 // its attempt to provide a fast path (and avoid a bytes.Buffer allocation) 215 // when escaping filenames is obviously a no-op. 216 testCases := map[string]string{ 217 "": "", 218 " ": " ", 219 "&": "&", 220 "*": "*", 221 "+": "+", 222 ",": ",", 223 "-": "-", 224 ".": ".", 225 "/": "/", 226 "0": "0", 227 "9": "9", 228 ":": ":", 229 "<": "<", 230 ">": ">", 231 "A": "A", 232 "_": "_", 233 "a": "a", 234 "~": "~", 235 "\u0201": "\u0201", 236 "&": "&amp;", 237 "foo&<b/ar>baz": "foo&<b/ar>baz", 238 } 239 240 for in, want := range testCases { 241 if got := escapeXML(in); got != want { 242 t.Errorf("in=%q: got %q, want %q", in, got, want) 243 } 244 } 245 } 246 247 func TestFilenameEscape(t *testing.T) { 248 hrefRe := regexp.MustCompile(`<D:href>([^<]*)</D:href>`) 249 displayNameRe := regexp.MustCompile(`<D:displayname>([^<]*)</D:displayname>`) 250 do := func(method, urlStr string) (string, string, error) { 251 req, err := http.NewRequest(method, urlStr, nil) 252 if err != nil { 253 return "", "", err 254 } 255 res, err := http.DefaultClient.Do(req) 256 if err != nil { 257 return "", "", err 258 } 259 defer res.Body.Close() 260 261 b, err := ioutil.ReadAll(res.Body) 262 if err != nil { 263 return "", "", err 264 } 265 hrefMatch := hrefRe.FindStringSubmatch(string(b)) 266 if len(hrefMatch) != 2 { 267 return "", "", errors.New("D:href not found") 268 } 269 displayNameMatch := displayNameRe.FindStringSubmatch(string(b)) 270 if len(displayNameMatch) != 2 { 271 return "", "", errors.New("D:displayname not found") 272 } 273 274 return hrefMatch[1], displayNameMatch[1], nil 275 } 276 277 testCases := []struct { 278 name, wantHref, wantDisplayName string 279 }{{ 280 name: `/foo%bar`, 281 wantHref: `/foo%25bar`, 282 wantDisplayName: `foo%bar`, 283 }, { 284 name: `/こんにちわ世界`, 285 wantHref: `/%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%82%8F%E4%B8%96%E7%95%8C`, 286 wantDisplayName: `こんにちわ世界`, 287 }, { 288 name: `/Program Files/`, 289 wantHref: `/Program%20Files/`, 290 wantDisplayName: `Program Files`, 291 }, { 292 name: `/go+lang`, 293 wantHref: `/go+lang`, 294 wantDisplayName: `go+lang`, 295 }, { 296 name: `/go&lang`, 297 wantHref: `/go&lang`, 298 wantDisplayName: `go&lang`, 299 }, { 300 name: `/go<lang`, 301 wantHref: `/go%3Clang`, 302 wantDisplayName: `go<lang`, 303 }, { 304 name: `/`, 305 wantHref: `/`, 306 wantDisplayName: ``, 307 }} 308 ctx := context.Background() 309 fs := NewMemFS() 310 for _, tc := range testCases { 311 if tc.name != "/" { 312 if strings.HasSuffix(tc.name, "/") { 313 if err := fs.Mkdir(ctx, tc.name, 0755); err != nil { 314 t.Fatalf("name=%q: Mkdir: %v", tc.name, err) 315 } 316 } else { 317 f, err := fs.OpenFile(ctx, tc.name, os.O_CREATE, 0644) 318 if err != nil { 319 t.Fatalf("name=%q: OpenFile: %v", tc.name, err) 320 } 321 f.Close() 322 } 323 } 324 } 325 326 srv := httptest.NewServer(&Handler{ 327 FileSystem: fs, 328 LockSystem: NewMemLS(), 329 }) 330 defer srv.Close() 331 332 u, err := url.Parse(srv.URL) 333 if err != nil { 334 t.Fatal(err) 335 } 336 337 for _, tc := range testCases { 338 u.Path = tc.name 339 gotHref, gotDisplayName, err := do("PROPFIND", u.String()) 340 if err != nil { 341 t.Errorf("name=%q: PROPFIND: %v", tc.name, err) 342 continue 343 } 344 if gotHref != tc.wantHref { 345 t.Errorf("name=%q: got href %q, want %q", tc.name, gotHref, tc.wantHref) 346 } 347 if gotDisplayName != tc.wantDisplayName { 348 t.Errorf("name=%q: got dispayname %q, want %q", tc.name, gotDisplayName, tc.wantDisplayName) 349 } 350 } 351 }