1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
20 "git.curoverse.com/arvados.git/lib/config"
21 "git.curoverse.com/arvados.git/sdk/go/arvados"
22 "git.curoverse.com/arvados.git/sdk/go/arvadostest"
23 "git.curoverse.com/arvados.git/sdk/go/auth"
24 "git.curoverse.com/arvados.git/sdk/go/keepclient"
25 check "gopkg.in/check.v1"
28 var _ = check.Suite(&UnitSuite{})
30 type UnitSuite struct {
31 Config *arvados.Config
34 func (s *UnitSuite) SetUpTest(c *check.C) {
35 ldr := config.NewLoader(bytes.NewBufferString("Clusters: {zzzzz: {}}"), nil)
37 cfg, err := ldr.Load()
38 c.Assert(err, check.IsNil)
42 func (s *UnitSuite) TestKeepClientBlockCache(c *check.C) {
43 cfg := newConfig(s.Config)
44 cfg.cluster.Collections.WebDAVCache.MaxBlockEntries = 42
45 h := handler{Config: cfg}
46 c.Check(keepclient.DefaultBlockCache.MaxBlocks, check.Not(check.Equals), cfg.cluster.Collections.WebDAVCache.MaxBlockEntries)
47 u := mustParseURL("http://keep-web.example/c=" + arvadostest.FooCollection + "/t=" + arvadostest.ActiveToken + "/foo")
52 RequestURI: u.RequestURI(),
54 resp := httptest.NewRecorder()
55 h.ServeHTTP(resp, req)
56 c.Check(resp.Code, check.Equals, http.StatusOK)
57 c.Check(keepclient.DefaultBlockCache.MaxBlocks, check.Equals, cfg.cluster.Collections.WebDAVCache.MaxBlockEntries)
60 func (s *UnitSuite) TestCORSPreflight(c *check.C) {
61 h := handler{Config: newConfig(s.Config)}
62 u := mustParseURL("http://keep-web.example/c=" + arvadostest.FooCollection + "/foo")
67 RequestURI: u.RequestURI(),
69 "Origin": {"https://workbench.example"},
70 "Access-Control-Request-Method": {"POST"},
74 // Check preflight for an allowed request
75 resp := httptest.NewRecorder()
76 h.ServeHTTP(resp, req)
77 c.Check(resp.Code, check.Equals, http.StatusOK)
78 c.Check(resp.Body.String(), check.Equals, "")
79 c.Check(resp.Header().Get("Access-Control-Allow-Origin"), check.Equals, "*")
80 c.Check(resp.Header().Get("Access-Control-Allow-Methods"), check.Equals, "COPY, DELETE, GET, LOCK, MKCOL, MOVE, OPTIONS, POST, PROPFIND, PROPPATCH, PUT, RMCOL, UNLOCK")
81 c.Check(resp.Header().Get("Access-Control-Allow-Headers"), check.Equals, "Authorization, Content-Type, Range, Depth, Destination, If, Lock-Token, Overwrite, Timeout")
83 // Check preflight for a disallowed request
84 resp = httptest.NewRecorder()
85 req.Header.Set("Access-Control-Request-Method", "MAKE-COFFEE")
86 h.ServeHTTP(resp, req)
87 c.Check(resp.Body.String(), check.Equals, "")
88 c.Check(resp.Code, check.Equals, http.StatusMethodNotAllowed)
91 func (s *UnitSuite) TestInvalidUUID(c *check.C) {
92 bogusID := strings.Replace(arvadostest.FooCollectionPDH, "+", "-", 1) + "-"
93 token := arvadostest.ActiveToken
94 for _, trial := range []string{
95 "http://keep-web/c=" + bogusID + "/foo",
96 "http://keep-web/c=" + bogusID + "/t=" + token + "/foo",
97 "http://keep-web/collections/download/" + bogusID + "/" + token + "/foo",
98 "http://keep-web/collections/" + bogusID + "/foo",
99 "http://" + bogusID + ".keep-web/" + bogusID + "/foo",
100 "http://" + bogusID + ".keep-web/t=" + token + "/" + bogusID + "/foo",
103 u := mustParseURL(trial)
104 req := &http.Request{
108 RequestURI: u.RequestURI(),
110 resp := httptest.NewRecorder()
111 cfg := newConfig(s.Config)
112 cfg.cluster.Users.AnonymousUserToken = arvadostest.AnonymousToken
113 h := handler{Config: cfg}
114 h.ServeHTTP(resp, req)
115 c.Check(resp.Code, check.Equals, http.StatusNotFound)
119 func mustParseURL(s string) *url.URL {
120 r, err := url.Parse(s)
122 panic("parse URL: " + s)
127 func (s *IntegrationSuite) TestVhost404(c *check.C) {
128 for _, testURL := range []string{
129 arvadostest.NonexistentCollection + ".example.com/theperthcountyconspiracy",
130 arvadostest.NonexistentCollection + ".example.com/t=" + arvadostest.ActiveToken + "/theperthcountyconspiracy",
132 resp := httptest.NewRecorder()
133 u := mustParseURL(testURL)
134 req := &http.Request{
137 RequestURI: u.RequestURI(),
139 s.testServer.Handler.ServeHTTP(resp, req)
140 c.Check(resp.Code, check.Equals, http.StatusNotFound)
141 c.Check(resp.Body.String(), check.Equals, "")
145 // An authorizer modifies an HTTP request to make use of the given
146 // token -- by adding it to a header, cookie, query param, or whatever
147 // -- and returns the HTTP status code we should expect from keep-web if
148 // the token is invalid.
149 type authorizer func(*http.Request, string) int
151 func (s *IntegrationSuite) TestVhostViaAuthzHeader(c *check.C) {
152 s.doVhostRequests(c, authzViaAuthzHeader)
154 func authzViaAuthzHeader(r *http.Request, tok string) int {
155 r.Header.Add("Authorization", "OAuth2 "+tok)
156 return http.StatusUnauthorized
159 func (s *IntegrationSuite) TestVhostViaCookieValue(c *check.C) {
160 s.doVhostRequests(c, authzViaCookieValue)
162 func authzViaCookieValue(r *http.Request, tok string) int {
163 r.AddCookie(&http.Cookie{
164 Name: "arvados_api_token",
165 Value: auth.EncodeTokenCookie([]byte(tok)),
167 return http.StatusUnauthorized
170 func (s *IntegrationSuite) TestVhostViaPath(c *check.C) {
171 s.doVhostRequests(c, authzViaPath)
173 func authzViaPath(r *http.Request, tok string) int {
174 r.URL.Path = "/t=" + tok + r.URL.Path
175 return http.StatusNotFound
178 func (s *IntegrationSuite) TestVhostViaQueryString(c *check.C) {
179 s.doVhostRequests(c, authzViaQueryString)
181 func authzViaQueryString(r *http.Request, tok string) int {
182 r.URL.RawQuery = "api_token=" + tok
183 return http.StatusUnauthorized
186 func (s *IntegrationSuite) TestVhostViaPOST(c *check.C) {
187 s.doVhostRequests(c, authzViaPOST)
189 func authzViaPOST(r *http.Request, tok string) int {
191 r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
192 r.Body = ioutil.NopCloser(strings.NewReader(
193 url.Values{"api_token": {tok}}.Encode()))
194 return http.StatusUnauthorized
197 func (s *IntegrationSuite) TestVhostViaXHRPOST(c *check.C) {
198 s.doVhostRequests(c, authzViaPOST)
200 func authzViaXHRPOST(r *http.Request, tok string) int {
202 r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
203 r.Header.Add("Origin", "https://origin.example")
204 r.Body = ioutil.NopCloser(strings.NewReader(
207 "disposition": {"attachment"},
209 return http.StatusUnauthorized
212 // Try some combinations of {url, token} using the given authorization
213 // mechanism, and verify the result is correct.
214 func (s *IntegrationSuite) doVhostRequests(c *check.C, authz authorizer) {
215 for _, hostPath := range []string{
216 arvadostest.FooCollection + ".example.com/foo",
217 arvadostest.FooCollection + "--collections.example.com/foo",
218 arvadostest.FooCollection + "--collections.example.com/_/foo",
219 arvadostest.FooCollectionPDH + ".example.com/foo",
220 strings.Replace(arvadostest.FooCollectionPDH, "+", "-", -1) + "--collections.example.com/foo",
221 arvadostest.FooBarDirCollection + ".example.com/dir1/foo",
223 c.Log("doRequests: ", hostPath)
224 s.doVhostRequestsWithHostPath(c, authz, hostPath)
228 func (s *IntegrationSuite) doVhostRequestsWithHostPath(c *check.C, authz authorizer, hostPath string) {
229 for _, tok := range []string{
230 arvadostest.ActiveToken,
231 arvadostest.ActiveToken[:15],
232 arvadostest.SpectatorToken,
236 u := mustParseURL("http://" + hostPath)
237 req := &http.Request{
241 RequestURI: u.RequestURI(),
242 Header: http.Header{},
244 failCode := authz(req, tok)
245 req, resp := s.doReq(req)
246 code, body := resp.Code, resp.Body.String()
248 // If the initial request had a (non-empty) token
249 // showing in the query string, we should have been
250 // redirected in order to hide it in a cookie.
251 c.Check(req.URL.String(), check.Not(check.Matches), `.*api_token=.+`)
253 if tok == arvadostest.ActiveToken {
254 c.Check(code, check.Equals, http.StatusOK)
255 c.Check(body, check.Equals, "foo")
258 c.Check(code >= 400, check.Equals, true)
259 c.Check(code < 500, check.Equals, true)
260 if tok == arvadostest.SpectatorToken {
261 // Valid token never offers to retry
262 // with different credentials.
263 c.Check(code, check.Equals, http.StatusNotFound)
265 // Invalid token can ask to retry
266 // depending on the authz method.
267 c.Check(code, check.Equals, failCode)
269 c.Check(body, check.Equals, "")
274 func (s *IntegrationSuite) doReq(req *http.Request) (*http.Request, *httptest.ResponseRecorder) {
275 resp := httptest.NewRecorder()
276 s.testServer.Handler.ServeHTTP(resp, req)
277 if resp.Code != http.StatusSeeOther {
280 cookies := (&http.Response{Header: resp.Header()}).Cookies()
281 u, _ := req.URL.Parse(resp.Header().Get("Location"))
286 RequestURI: u.RequestURI(),
287 Header: http.Header{},
289 for _, c := range cookies {
295 func (s *IntegrationSuite) TestVhostRedirectQueryTokenToCookie(c *check.C) {
296 s.testVhostRedirectTokenToCookie(c, "GET",
297 arvadostest.FooCollection+".example.com/foo",
298 "?api_token="+arvadostest.ActiveToken,
306 func (s *IntegrationSuite) TestSingleOriginSecretLink(c *check.C) {
307 s.testVhostRedirectTokenToCookie(c, "GET",
308 "example.com/c="+arvadostest.FooCollection+"/t="+arvadostest.ActiveToken+"/foo",
317 // Bad token in URL is 404 Not Found because it doesn't make sense to
318 // retry the same URL with different authorization.
319 func (s *IntegrationSuite) TestSingleOriginSecretLinkBadToken(c *check.C) {
320 s.testVhostRedirectTokenToCookie(c, "GET",
321 "example.com/c="+arvadostest.FooCollection+"/t=bogus/foo",
330 // Bad token in a cookie (even if it got there via our own
331 // query-string-to-cookie redirect) is, in principle, retryable at the
332 // same URL so it's 401 Unauthorized.
333 func (s *IntegrationSuite) TestVhostRedirectQueryTokenToBogusCookie(c *check.C) {
334 s.testVhostRedirectTokenToCookie(c, "GET",
335 arvadostest.FooCollection+".example.com/foo",
336 "?api_token=thisisabogustoken",
339 http.StatusUnauthorized,
344 func (s *IntegrationSuite) TestVhostRedirectQueryTokenSingleOriginError(c *check.C) {
345 s.testVhostRedirectTokenToCookie(c, "GET",
346 "example.com/c="+arvadostest.FooCollection+"/foo",
347 "?api_token="+arvadostest.ActiveToken,
350 http.StatusBadRequest,
355 // If client requests an attachment by putting ?disposition=attachment
356 // in the query string, and gets redirected, the redirect target
357 // should respond with an attachment.
358 func (s *IntegrationSuite) TestVhostRedirectQueryTokenRequestAttachment(c *check.C) {
359 resp := s.testVhostRedirectTokenToCookie(c, "GET",
360 arvadostest.FooCollection+".example.com/foo",
361 "?disposition=attachment&api_token="+arvadostest.ActiveToken,
367 c.Check(resp.Header().Get("Content-Disposition"), check.Matches, "attachment(;.*)?")
370 func (s *IntegrationSuite) TestVhostRedirectQueryTokenSiteFS(c *check.C) {
371 s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "download.example.com"
372 resp := s.testVhostRedirectTokenToCookie(c, "GET",
373 "download.example.com/by_id/"+arvadostest.FooCollection+"/foo",
374 "?api_token="+arvadostest.ActiveToken,
380 c.Check(resp.Header().Get("Content-Disposition"), check.Matches, "attachment(;.*)?")
383 func (s *IntegrationSuite) TestPastCollectionVersionFileAccess(c *check.C) {
384 s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "download.example.com"
385 resp := s.testVhostRedirectTokenToCookie(c, "GET",
386 "download.example.com/c="+arvadostest.WazVersion1Collection+"/waz",
387 "?api_token="+arvadostest.ActiveToken,
393 c.Check(resp.Header().Get("Content-Disposition"), check.Matches, "attachment(;.*)?")
394 resp = s.testVhostRedirectTokenToCookie(c, "GET",
395 "download.example.com/by_id/"+arvadostest.WazVersion1Collection+"/waz",
396 "?api_token="+arvadostest.ActiveToken,
402 c.Check(resp.Header().Get("Content-Disposition"), check.Matches, "attachment(;.*)?")
405 func (s *IntegrationSuite) TestVhostRedirectQueryTokenTrustAllContent(c *check.C) {
406 s.testServer.Config.cluster.Collections.TrustAllContent = true
407 s.testVhostRedirectTokenToCookie(c, "GET",
408 "example.com/c="+arvadostest.FooCollection+"/foo",
409 "?api_token="+arvadostest.ActiveToken,
417 func (s *IntegrationSuite) TestVhostRedirectQueryTokenAttachmentOnlyHost(c *check.C) {
418 s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "example.com:1234"
420 s.testVhostRedirectTokenToCookie(c, "GET",
421 "example.com/c="+arvadostest.FooCollection+"/foo",
422 "?api_token="+arvadostest.ActiveToken,
425 http.StatusBadRequest,
429 resp := s.testVhostRedirectTokenToCookie(c, "GET",
430 "example.com:1234/c="+arvadostest.FooCollection+"/foo",
431 "?api_token="+arvadostest.ActiveToken,
437 c.Check(resp.Header().Get("Content-Disposition"), check.Equals, "attachment")
440 func (s *IntegrationSuite) TestVhostRedirectPOSTFormTokenToCookie(c *check.C) {
441 s.testVhostRedirectTokenToCookie(c, "POST",
442 arvadostest.FooCollection+".example.com/foo",
444 "application/x-www-form-urlencoded",
445 url.Values{"api_token": {arvadostest.ActiveToken}}.Encode(),
451 func (s *IntegrationSuite) TestVhostRedirectPOSTFormTokenToCookie404(c *check.C) {
452 s.testVhostRedirectTokenToCookie(c, "POST",
453 arvadostest.FooCollection+".example.com/foo",
455 "application/x-www-form-urlencoded",
456 url.Values{"api_token": {arvadostest.SpectatorToken}}.Encode(),
462 func (s *IntegrationSuite) TestAnonymousTokenOK(c *check.C) {
463 s.testServer.Config.cluster.Users.AnonymousUserToken = arvadostest.AnonymousToken
464 s.testVhostRedirectTokenToCookie(c, "GET",
465 "example.com/c="+arvadostest.HelloWorldCollection+"/Hello%20world.txt",
474 func (s *IntegrationSuite) TestAnonymousTokenError(c *check.C) {
475 s.testServer.Config.cluster.Users.AnonymousUserToken = "anonymousTokenConfiguredButInvalid"
476 s.testVhostRedirectTokenToCookie(c, "GET",
477 "example.com/c="+arvadostest.HelloWorldCollection+"/Hello%20world.txt",
486 func (s *IntegrationSuite) TestSpecialCharsInPath(c *check.C) {
487 s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "download.example.com"
489 client := s.testServer.Config.Client
490 client.AuthToken = arvadostest.ActiveToken
491 fs, err := (&arvados.Collection{}).FileSystem(&client, nil)
492 c.Assert(err, check.IsNil)
493 f, err := fs.OpenFile("https:\\\"odd' path chars", os.O_CREATE, 0777)
494 c.Assert(err, check.IsNil)
496 mtxt, err := fs.MarshalManifest(".")
497 c.Assert(err, check.IsNil)
498 var coll arvados.Collection
499 err = client.RequestAndDecode(&coll, "POST", "arvados/v1/collections", nil, map[string]interface{}{
500 "collection": map[string]string{
501 "manifest_text": mtxt,
504 c.Assert(err, check.IsNil)
506 u, _ := url.Parse("http://download.example.com/c=" + coll.UUID + "/")
507 req := &http.Request{
511 RequestURI: u.RequestURI(),
513 "Authorization": {"Bearer " + client.AuthToken},
516 resp := httptest.NewRecorder()
517 s.testServer.Handler.ServeHTTP(resp, req)
518 c.Check(resp.Code, check.Equals, http.StatusOK)
519 c.Check(resp.Body.String(), check.Matches, `(?ms).*href="./https:%5c%22odd%27%20path%20chars"\S+https:\\"odd' path chars.*`)
522 // XHRs can't follow redirect-with-cookie so they rely on method=POST
523 // and disposition=attachment (telling us it's acceptable to respond
524 // with content instead of a redirect) and an Origin header that gets
525 // added automatically by the browser (telling us it's desirable to do
527 func (s *IntegrationSuite) TestXHRNoRedirect(c *check.C) {
528 u, _ := url.Parse("http://example.com/c=" + arvadostest.FooCollection + "/foo")
529 req := &http.Request{
533 RequestURI: u.RequestURI(),
535 "Origin": {"https://origin.example"},
536 "Content-Type": {"application/x-www-form-urlencoded"},
538 Body: ioutil.NopCloser(strings.NewReader(url.Values{
539 "api_token": {arvadostest.ActiveToken},
540 "disposition": {"attachment"},
543 resp := httptest.NewRecorder()
544 s.testServer.Handler.ServeHTTP(resp, req)
545 c.Check(resp.Code, check.Equals, http.StatusOK)
546 c.Check(resp.Body.String(), check.Equals, "foo")
547 c.Check(resp.Header().Get("Access-Control-Allow-Origin"), check.Equals, "*")
550 func (s *IntegrationSuite) testVhostRedirectTokenToCookie(c *check.C, method, hostPath, queryString, contentType, reqBody string, expectStatus int, expectRespBody string) *httptest.ResponseRecorder {
551 u, _ := url.Parse(`http://` + hostPath + queryString)
552 req := &http.Request{
556 RequestURI: u.RequestURI(),
557 Header: http.Header{"Content-Type": {contentType}},
558 Body: ioutil.NopCloser(strings.NewReader(reqBody)),
561 resp := httptest.NewRecorder()
563 c.Check(resp.Code, check.Equals, expectStatus)
564 c.Check(resp.Body.String(), check.Equals, expectRespBody)
567 s.testServer.Handler.ServeHTTP(resp, req)
568 if resp.Code != http.StatusSeeOther {
571 c.Check(resp.Body.String(), check.Matches, `.*href="http://`+regexp.QuoteMeta(html.EscapeString(hostPath))+`(\?[^"]*)?".*`)
572 cookies := (&http.Response{Header: resp.Header()}).Cookies()
574 u, _ = u.Parse(resp.Header().Get("Location"))
579 RequestURI: u.RequestURI(),
580 Header: http.Header{},
582 for _, c := range cookies {
586 resp = httptest.NewRecorder()
587 s.testServer.Handler.ServeHTTP(resp, req)
588 c.Check(resp.Header().Get("Location"), check.Equals, "")
592 func (s *IntegrationSuite) TestDirectoryListingWithAnonymousToken(c *check.C) {
593 s.testServer.Config.cluster.Users.AnonymousUserToken = arvadostest.AnonymousToken
594 s.testDirectoryListing(c)
597 func (s *IntegrationSuite) TestDirectoryListingWithNoAnonymousToken(c *check.C) {
598 s.testServer.Config.cluster.Users.AnonymousUserToken = ""
599 s.testDirectoryListing(c)
602 func (s *IntegrationSuite) testDirectoryListing(c *check.C) {
603 s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "download.example.com"
604 authHeader := http.Header{
605 "Authorization": {"OAuth2 " + arvadostest.ActiveToken},
607 for _, trial := range []struct {
615 uri: strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + ".example.com/",
617 expect: []string{"dir1/foo", "dir1/bar"},
621 uri: strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + ".example.com/dir1/",
623 expect: []string{"foo", "bar"},
627 // URLs of this form ignore authHeader, and
628 // FooAndBarFilesInDirUUID isn't public, so
630 uri: "download.example.com/collections/" + arvadostest.FooAndBarFilesInDirUUID + "/",
635 uri: "download.example.com/users/active/foo_file_in_dir/",
637 expect: []string{"dir1/"},
641 uri: "download.example.com/users/active/foo_file_in_dir/dir1/",
643 expect: []string{"bar"},
647 uri: "download.example.com/",
649 expect: []string{"users/"},
653 uri: "download.example.com/users",
656 expect: []string{"active/"},
660 uri: "download.example.com/users/",
662 expect: []string{"active/"},
666 uri: "download.example.com/users/active",
668 redirect: "/users/active/",
669 expect: []string{"foo_file_in_dir/"},
673 uri: "download.example.com/users/active/",
675 expect: []string{"foo_file_in_dir/"},
679 uri: "collections.example.com/collections/download/" + arvadostest.FooAndBarFilesInDirUUID + "/" + arvadostest.ActiveToken + "/",
681 expect: []string{"dir1/foo", "dir1/bar"},
685 uri: "collections.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/t=" + arvadostest.ActiveToken + "/",
687 expect: []string{"dir1/foo", "dir1/bar"},
691 uri: "collections.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/t=" + arvadostest.ActiveToken,
693 expect: []string{"dir1/foo", "dir1/bar"},
697 uri: "download.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID,
699 expect: []string{"dir1/foo", "dir1/bar"},
703 uri: "download.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/dir1",
705 redirect: "/c=" + arvadostest.FooAndBarFilesInDirUUID + "/dir1/",
706 expect: []string{"foo", "bar"},
710 uri: "download.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/_/dir1/",
712 expect: []string{"foo", "bar"},
716 uri: arvadostest.FooAndBarFilesInDirUUID + ".example.com/dir1?api_token=" + arvadostest.ActiveToken,
719 expect: []string{"foo", "bar"},
723 uri: "collections.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/theperthcountyconspiracydoesnotexist/",
728 uri: "download.example.com/c=" + arvadostest.WazVersion1Collection,
730 expect: []string{"waz"},
734 uri: "download.example.com/by_id/" + arvadostest.WazVersion1Collection,
736 expect: []string{"waz"},
740 comment := check.Commentf("HTML: %q => %q", trial.uri, trial.expect)
741 resp := httptest.NewRecorder()
742 u := mustParseURL("//" + trial.uri)
743 req := &http.Request{
747 RequestURI: u.RequestURI(),
748 Header: copyHeader(trial.header),
750 s.testServer.Handler.ServeHTTP(resp, req)
751 var cookies []*http.Cookie
752 for resp.Code == http.StatusSeeOther {
753 u, _ := req.URL.Parse(resp.Header().Get("Location"))
758 RequestURI: u.RequestURI(),
759 Header: copyHeader(trial.header),
761 cookies = append(cookies, (&http.Response{Header: resp.Header()}).Cookies()...)
762 for _, c := range cookies {
765 resp = httptest.NewRecorder()
766 s.testServer.Handler.ServeHTTP(resp, req)
768 if trial.redirect != "" {
769 c.Check(req.URL.Path, check.Equals, trial.redirect, comment)
771 if trial.expect == nil {
772 c.Check(resp.Code, check.Equals, http.StatusNotFound, comment)
774 c.Check(resp.Code, check.Equals, http.StatusOK, comment)
775 for _, e := range trial.expect {
776 c.Check(resp.Body.String(), check.Matches, `(?ms).*href="./`+e+`".*`, comment)
778 c.Check(resp.Body.String(), check.Matches, `(?ms).*--cut-dirs=`+fmt.Sprintf("%d", trial.cutDirs)+` .*`, comment)
781 comment = check.Commentf("WebDAV: %q => %q", trial.uri, trial.expect)
786 RequestURI: u.RequestURI(),
787 Header: copyHeader(trial.header),
788 Body: ioutil.NopCloser(&bytes.Buffer{}),
790 resp = httptest.NewRecorder()
791 s.testServer.Handler.ServeHTTP(resp, req)
792 if trial.expect == nil {
793 c.Check(resp.Code, check.Equals, http.StatusNotFound, comment)
795 c.Check(resp.Code, check.Equals, http.StatusOK, comment)
802 RequestURI: u.RequestURI(),
803 Header: copyHeader(trial.header),
804 Body: ioutil.NopCloser(&bytes.Buffer{}),
806 resp = httptest.NewRecorder()
807 s.testServer.Handler.ServeHTTP(resp, req)
808 if trial.expect == nil {
809 c.Check(resp.Code, check.Equals, http.StatusNotFound, comment)
811 c.Check(resp.Code, check.Equals, http.StatusMultiStatus, comment)
812 for _, e := range trial.expect {
813 c.Check(resp.Body.String(), check.Matches, `(?ms).*<D:href>`+filepath.Join(u.Path, e)+`</D:href>.*`, comment)
819 func (s *IntegrationSuite) TestDeleteLastFile(c *check.C) {
820 arv := arvados.NewClientFromEnv()
821 var newCollection arvados.Collection
822 err := arv.RequestAndDecode(&newCollection, "POST", "arvados/v1/collections", nil, map[string]interface{}{
823 "collection": map[string]string{
824 "owner_uuid": arvadostest.ActiveUserUUID,
825 "manifest_text": ". acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo.txt 0:3:bar.txt\n",
826 "name": "keep-web test collection",
828 "ensure_unique_name": true,
830 c.Assert(err, check.IsNil)
831 defer arv.RequestAndDecode(&newCollection, "DELETE", "arvados/v1/collections/"+newCollection.UUID, nil, nil)
833 var updated arvados.Collection
834 for _, fnm := range []string{"foo.txt", "bar.txt"} {
835 s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "example.com"
836 u, _ := url.Parse("http://example.com/c=" + newCollection.UUID + "/" + fnm)
837 req := &http.Request{
841 RequestURI: u.RequestURI(),
843 "Authorization": {"Bearer " + arvadostest.ActiveToken},
846 resp := httptest.NewRecorder()
847 s.testServer.Handler.ServeHTTP(resp, req)
848 c.Check(resp.Code, check.Equals, http.StatusNoContent)
850 updated = arvados.Collection{}
851 err = arv.RequestAndDecode(&updated, "GET", "arvados/v1/collections/"+newCollection.UUID, nil, nil)
852 c.Check(err, check.IsNil)
853 c.Check(updated.ManifestText, check.Not(check.Matches), `(?ms).*\Q`+fnm+`\E.*`)
854 c.Logf("updated manifest_text %q", updated.ManifestText)
856 c.Check(updated.ManifestText, check.Equals, "")
859 func (s *IntegrationSuite) TestHealthCheckPing(c *check.C) {
860 s.testServer.Config.cluster.ManagementToken = arvadostest.ManagementToken
861 authHeader := http.Header{
862 "Authorization": {"Bearer " + arvadostest.ManagementToken},
865 resp := httptest.NewRecorder()
866 u := mustParseURL("http://download.example.com/_health/ping")
867 req := &http.Request{
871 RequestURI: u.RequestURI(),
874 s.testServer.Handler.ServeHTTP(resp, req)
876 c.Check(resp.Code, check.Equals, http.StatusOK)
877 c.Check(resp.Body.String(), check.Matches, `{"health":"OK"}\n`)
880 func copyHeader(h http.Header) http.Header {
882 for k, v := range h {
883 hc[k] = append([]string(nil), v...)