11 "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
12 "git.curoverse.com/arvados.git/sdk/go/auth"
13 "git.curoverse.com/arvados.git/sdk/go/httpserver"
16 var clientPool = arvadosclient.MakeClientPool()
18 var anonymousTokens []string
23 // TODO(TC): Get anonymousTokens from flags
24 anonymousTokens = []string{}
27 func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
31 w := httpserver.WrapResponseWriter(wOrig)
34 if w.WroteStatus() == 0 {
35 w.WriteHeader(statusCode)
37 httpserver.Log(r.RemoteAddr, "WARNING",
38 fmt.Sprintf("Our status changed from %d to %d after we sent headers", w.WroteStatus(), statusCode))
42 statusText = http.StatusText(statusCode)
44 httpserver.Log(r.RemoteAddr, statusCode, statusText, w.WroteBodyBytes(), r.Method, r.URL.Path)
47 arv := clientPool.Get()
49 statusCode, statusText = http.StatusInternalServerError, "Pool failed: "+clientPool.Err().Error()
52 defer clientPool.Put(arv)
54 pathParts := strings.Split(r.URL.Path[1:], "/")
56 if len(pathParts) < 3 || pathParts[0] != "collections" || pathParts[1] == "" || pathParts[2] == "" {
57 statusCode = http.StatusNotFound
62 var targetPath []string
64 var reqTokens []string
66 if len(pathParts) >= 5 && pathParts[1] == "download" {
67 // "/collections/download/{id}/{token}/path..." form:
68 // Don't use our configured anonymous tokens,
69 // Authorization headers, etc. Just use the token in
71 targetId = pathParts[2]
72 tokens = []string{pathParts[3]}
73 targetPath = pathParts[4:]
76 // "/collections/{id}/path..." form
77 targetId = pathParts[1]
78 reqTokens = auth.NewCredentialsFromHTTPRequest(r).Tokens
79 tokens = append(reqTokens, anonymousTokens...)
80 targetPath = pathParts[2:]
83 tokenResult := make(map[string]int)
84 collection := make(map[string]interface{})
86 for _, arv.ApiToken = range tokens {
87 err := arv.Get("collections", targetId, nil, &collection)
94 if srvErr, ok := err.(arvadosclient.APIServerError); ok {
95 switch srvErr.HttpStatusCode {
97 // Token broken or insufficient to
98 // retrieve collection
99 tokenResult[arv.ApiToken] = srvErr.HttpStatusCode
103 // Something more serious is wrong
104 statusCode, statusText = http.StatusInternalServerError, err.Error()
109 // The URL is a "secret sharing link", but it
110 // didn't work out. Asking the client for
111 // additional credentials would just be
113 statusCode = http.StatusNotFound
116 for _, t := range reqTokens {
117 if tokenResult[t] == 404 {
118 // The client provided valid token(s), but the
119 // collection was not found.
120 statusCode = http.StatusNotFound
124 // The client's token was invalid (e.g., expired), or
125 // the client didn't even provide one. Propagate the
126 // 401 to encourage the client to use a [different]
129 // TODO(TC): This response would be confusing to
130 // someone trying (anonymously) to download public
131 // data that has been deleted. Allow a referrer to
132 // provide this context somehow?
133 statusCode = http.StatusUnauthorized
134 w.Header().Add("WWW-Authenticate", "Basic realm=\"dl\"")
138 filename := strings.Join(targetPath, "/")
139 rdr, err := arvadosclient.CollectionFileReader(collection, filename)
140 if os.IsNotExist(err) {
141 statusCode = http.StatusNotFound
143 } else if err == arvadosclient.ErrNotImplemented {
144 statusCode = http.StatusNotImplemented
146 } else if err != nil {
147 statusCode, statusText = http.StatusBadGateway, err.Error()
151 // One or both of these can be -1 if not found:
152 basenamePos := strings.LastIndex(filename, "/")
153 extPos := strings.LastIndex(filename, ".")
154 if extPos > basenamePos {
155 // Now extPos is safely >= 0.
156 if t := mime.TypeByExtension(filename[extPos:]); t != "" {
157 w.Header().Set("Content-Type", t)
161 _, err = io.Copy(w, rdr)
163 statusCode, statusText = http.StatusBadGateway, err.Error()