// (``https://dl.example.com/'') and upload it to some other site
// chosen by the author of collection X.
//
+// Attachment-Only host
+//
+// It is possible to serve untrusted content and accept user
+// credentials at the same origin as long as the content is only
+// downloaded, never executed by browsers. A single origin (hostname
+// and port) can be designated as an "attachment-only" origin: cookies
+// will be accepted and all responses will have a
+// "Content-Disposition: attachment" header. This behavior is invoked
+// only when the designated origin matches exactly the Host header
+// provided by the client or upstream proxy.
+//
+// keep-web -attachment-only-host domain.example:9999
+//
// Trust All Content mode
//
// In "trust all content" mode, Keep-web will accept credentials (API
clientPool = arvadosclient.MakeClientPool()
trustAllContent = false
anonymousTokens []string
+ attachmentOnlyHost = ""
)
func init() {
flag.BoolVar(&trustAllContent, "trust-all-content", false,
"Serve non-public content from a single origin. Dangerous: read docs before using!")
+ flag.StringVar(&attachmentOnlyHost, "attachment-only-host", "",
+ "Accept credentials, and add \"Content-Disposition: attachment\" response headers, for requests at this hostname:port. Prohibiting inline display makes it possible to serve untrusted and non-public content from a single origin, i.e., without wildcard DNS or SSL.")
}
// return a UUID or PDH if s begins with a UUID or URL-encoded PDH;
var tokens []string
var reqTokens []string
var pathToken bool
+ var attachment bool
credentialsOK := trustAllContent
+ if r.Host != "" && r.Host == attachmentOnlyHost {
+ credentialsOK = true
+ attachment = true
+ } else if r.FormValue("disposition") == "attachment" {
+ attachment = true
+ }
+
if targetId = parseCollectionIdFromDNSName(r.Host); targetId != "" {
// http://ID.dl.example/PATH...
credentialsOK = true
}
}
w.Header().Set("Content-Length", fmt.Sprintf("%d", rdr.Len()))
+ if attachment {
+ w.Header().Set("Content-Disposition", "attachment")
+ }
w.WriteHeader(http.StatusOK)
_, err = io.Copy(w, rdr)
)
}
+func (s *IntegrationSuite) TestVhostRedirectQueryTokenAttachmentOnlyHost(c *check.C) {
+ defer func(orig string) {
+ attachmentOnlyHost = orig
+ }(attachmentOnlyHost)
+ attachmentOnlyHost = "example.com:1234"
+
+ s.testVhostRedirectTokenToCookie(c, "GET",
+ "example.com/c=" + arvadostest.FooCollection + "/foo",
+ "?api_token=" + arvadostest.ActiveToken,
+ "text/plain",
+ "",
+ http.StatusBadRequest,
+ )
+
+ resp := s.testVhostRedirectTokenToCookie(c, "GET",
+ "example.com:1234/c=" + arvadostest.FooCollection + "/foo",
+ "?api_token=" + arvadostest.ActiveToken,
+ "text/plain",
+ "",
+ http.StatusOK,
+ )
+ c.Check(resp.Header().Get("Content-Disposition"), check.Equals, "attachment")
+}
+
func (s *IntegrationSuite) TestVhostRedirectPOSTFormTokenToCookie(c *check.C) {
s.testVhostRedirectTokenToCookie(c, "POST",
arvadostest.FooCollection + ".example.com/foo",
)
}
-func (s *IntegrationSuite) testVhostRedirectTokenToCookie(c *check.C, method, hostPath, queryString, contentType, body string, expectStatus int) {
+func (s *IntegrationSuite) testVhostRedirectTokenToCookie(c *check.C, method, hostPath, queryString, contentType, body string, expectStatus int) *httptest.ResponseRecorder {
u, _ := url.Parse(`http://` + hostPath + queryString)
req := &http.Request{
Method: method,
(&handler{}).ServeHTTP(resp, req)
if resp.Code != http.StatusSeeOther {
c.Assert(resp.Code, check.Equals, expectStatus)
- return
+ return resp
}
c.Check(resp.Body.String(), check.Matches, `.*href="//` + regexp.QuoteMeta(html.EscapeString(hostPath)) + `".*`)
cookies := (&http.Response{Header: resp.Header()}).Cookies()
if expectStatus == http.StatusOK {
c.Check(resp.Body.String(), check.Equals, "foo")
}
+ return resp
}