+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
package main
import (
+ "fmt"
"html"
"io/ioutil"
"net/http"
type UnitSuite struct{}
+func (s *UnitSuite) TestCORSPreflight(c *check.C) {
+ h := handler{Config: DefaultConfig()}
+ u, _ := url.Parse("http://keep-web.example/c=" + arvadostest.FooCollection + "/foo")
+ req := &http.Request{
+ Method: "OPTIONS",
+ Host: u.Host,
+ URL: u,
+ RequestURI: u.RequestURI(),
+ Header: http.Header{
+ "Origin": {"https://workbench.example"},
+ "Access-Control-Request-Method": {"POST"},
+ },
+ }
+
+ // Check preflight for an allowed request
+ resp := httptest.NewRecorder()
+ h.ServeHTTP(resp, req)
+ c.Check(resp.Code, check.Equals, http.StatusOK)
+ c.Check(resp.Body.String(), check.Equals, "")
+ c.Check(resp.Header().Get("Access-Control-Allow-Origin"), check.Equals, "*")
+ c.Check(resp.Header().Get("Access-Control-Allow-Methods"), check.Equals, "GET, POST")
+ c.Check(resp.Header().Get("Access-Control-Allow-Headers"), check.Equals, "Range")
+
+ // Check preflight for a disallowed request
+ resp = httptest.NewRecorder()
+ req.Header.Set("Access-Control-Request-Method", "DELETE")
+ h.ServeHTTP(resp, req)
+ c.Check(resp.Body.String(), check.Equals, "")
+ c.Check(resp.Code, check.Equals, http.StatusMethodNotAllowed)
+}
+
+func (s *UnitSuite) TestInvalidUUID(c *check.C) {
+ bogusID := strings.Replace(arvadostest.FooPdh, "+", "-", 1) + "-"
+ token := arvadostest.ActiveToken
+ for _, trial := range []string{
+ "http://keep-web/c=" + bogusID + "/foo",
+ "http://keep-web/c=" + bogusID + "/t=" + token + "/foo",
+ "http://keep-web/collections/download/" + bogusID + "/" + token + "/foo",
+ "http://keep-web/collections/" + bogusID + "/foo",
+ "http://" + bogusID + ".keep-web/" + bogusID + "/foo",
+ "http://" + bogusID + ".keep-web/t=" + token + "/" + bogusID + "/foo",
+ } {
+ c.Log(trial)
+ u, err := url.Parse(trial)
+ c.Assert(err, check.IsNil)
+ req := &http.Request{
+ Method: "GET",
+ Host: u.Host,
+ URL: u,
+ RequestURI: u.RequestURI(),
+ }
+ resp := httptest.NewRecorder()
+ cfg := DefaultConfig()
+ cfg.AnonymousTokens = []string{arvadostest.AnonymousToken}
+ h := handler{Config: cfg}
+ h.ServeHTTP(resp, req)
+ c.Check(resp.Code, check.Equals, http.StatusNotFound)
+ }
+}
+
func mustParseURL(s string) *url.URL {
r, err := url.Parse(s)
if err != nil {
)
}
-func (s *IntegrationSuite) TestRange(c *check.C) {
- s.testServer.Config.AnonymousTokens = []string{arvadostest.AnonymousToken}
- u, _ := url.Parse("http://example.com/c=" + arvadostest.HelloWorldCollection + "/Hello%20world.txt")
- req := &http.Request{
- Method: "GET",
- Host: u.Host,
- URL: u,
- RequestURI: u.RequestURI(),
- Header: http.Header{"Range": {"bytes=0-4"}},
- }
- resp := httptest.NewRecorder()
- s.testServer.Handler.ServeHTTP(resp, req)
- c.Check(resp.Code, check.Equals, http.StatusPartialContent)
- c.Check(resp.Body.String(), check.Equals, "Hello")
- c.Check(resp.Header().Get("Content-Length"), check.Equals, "5")
- c.Check(resp.Header().Get("Content-Range"), check.Equals, "bytes 0-4/12")
-
- req.Header.Set("Range", "bytes=0-")
- resp = httptest.NewRecorder()
- s.testServer.Handler.ServeHTTP(resp, req)
- // 200 and 206 are both correct:
- c.Check(resp.Code, check.Equals, http.StatusOK)
- c.Check(resp.Body.String(), check.Equals, "Hello world\n")
- c.Check(resp.Header().Get("Content-Length"), check.Equals, "12")
-
- // Unsupported ranges are ignored
- for _, hdr := range []string{
- "bytes=5-5", // non-zero start byte
- "bytes=-5", // last 5 bytes
- "cubits=0-5", // unsupported unit
- "bytes=0-340282366920938463463374607431768211456", // 2^128
- } {
- req.Header.Set("Range", hdr)
- resp = httptest.NewRecorder()
- s.testServer.Handler.ServeHTTP(resp, req)
- c.Check(resp.Code, check.Equals, http.StatusOK)
- c.Check(resp.Body.String(), check.Equals, "Hello world\n")
- c.Check(resp.Header().Get("Content-Length"), check.Equals, "12")
- c.Check(resp.Header().Get("Content-Range"), check.Equals, "")
- c.Check(resp.Header().Get("Accept-Ranges"), check.Equals, "bytes")
- }
-}
-
// XHRs can't follow redirect-with-cookie so they rely on method=POST
// and disposition=attachment (telling us it's acceptable to respond
// with content instead of a redirect) and an Origin header that gets
c.Check(resp.Header().Get("Location"), check.Equals, "")
return resp
}
+
+func (s *IntegrationSuite) TestDirectoryListing(c *check.C) {
+ s.testServer.Config.AttachmentOnlyHost = "download.example.com"
+ authHeader := http.Header{
+ "Authorization": {"OAuth2 " + arvadostest.ActiveToken},
+ }
+ for _, trial := range []struct {
+ uri string
+ header http.Header
+ expect []string
+ cutDirs int
+ }{
+ {
+ uri: strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + ".example.com/",
+ header: authHeader,
+ expect: []string{"dir1/foo", "dir1/bar"},
+ cutDirs: 0,
+ },
+ {
+ uri: strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + ".example.com/dir1/",
+ header: authHeader,
+ expect: []string{"foo", "bar"},
+ cutDirs: 0,
+ },
+ {
+ uri: "download.example.com/collections/" + arvadostest.FooAndBarFilesInDirUUID + "/",
+ header: authHeader,
+ expect: []string{"dir1/foo", "dir1/bar"},
+ cutDirs: 2,
+ },
+ {
+ uri: "collections.example.com/collections/download/" + arvadostest.FooAndBarFilesInDirUUID + "/" + arvadostest.ActiveToken + "/",
+ header: nil,
+ expect: []string{"dir1/foo", "dir1/bar"},
+ cutDirs: 4,
+ },
+ {
+ uri: "collections.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/t=" + arvadostest.ActiveToken + "/",
+ header: nil,
+ expect: []string{"dir1/foo", "dir1/bar"},
+ cutDirs: 2,
+ },
+ {
+ uri: "download.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/dir1/",
+ header: authHeader,
+ expect: []string{"foo", "bar"},
+ cutDirs: 1,
+ },
+ {
+ uri: "download.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/_/dir1/",
+ header: authHeader,
+ expect: []string{"foo", "bar"},
+ cutDirs: 2,
+ },
+ {
+ uri: arvadostest.FooAndBarFilesInDirUUID + ".example.com/dir1?api_token=" + arvadostest.ActiveToken,
+ header: authHeader,
+ expect: []string{"foo", "bar"},
+ cutDirs: 0,
+ },
+ {
+ uri: "collections.example.com/c=" + arvadostest.FooAndBarFilesInDirUUID + "/theperthcountyconspiracydoesnotexist/",
+ header: authHeader,
+ expect: nil,
+ },
+ } {
+ c.Logf("%q => %q", trial.uri, trial.expect)
+ resp := httptest.NewRecorder()
+ u := mustParseURL("//" + trial.uri)
+ req := &http.Request{
+ Method: "GET",
+ Host: u.Host,
+ URL: u,
+ RequestURI: u.RequestURI(),
+ Header: trial.header,
+ }
+ s.testServer.Handler.ServeHTTP(resp, req)
+ var cookies []*http.Cookie
+ for resp.Code == http.StatusSeeOther {
+ u, _ := req.URL.Parse(resp.Header().Get("Location"))
+ req = &http.Request{
+ Method: "GET",
+ Host: u.Host,
+ URL: u,
+ RequestURI: u.RequestURI(),
+ Header: http.Header{},
+ }
+ cookies = append(cookies, (&http.Response{Header: resp.Header()}).Cookies()...)
+ for _, c := range cookies {
+ req.AddCookie(c)
+ }
+ resp = httptest.NewRecorder()
+ s.testServer.Handler.ServeHTTP(resp, req)
+ }
+ if trial.expect == nil {
+ c.Check(resp.Code, check.Equals, http.StatusNotFound)
+ } else {
+ c.Check(resp.Code, check.Equals, http.StatusOK)
+ for _, e := range trial.expect {
+ c.Check(resp.Body.String(), check.Matches, `(?ms).*href="`+e+`".*`)
+ }
+ c.Check(resp.Body.String(), check.Matches, `(?ms).*--cut-dirs=`+fmt.Sprintf("%d", trial.cutDirs)+` .*`)
+ }
+ }
+}