7829: Remove unnecessary MANIFEST.in files.
[arvados.git] / services / keep-web / handler.go
index 21d58b7e029cae8e1a82b4737831e88addad9847..847329f33137ca259b0399349e539cce01885172 100644 (file)
@@ -9,8 +9,9 @@ import (
        "net/http"
        "net/url"
        "os"
+       "regexp"
+       "strconv"
        "strings"
-       "time"
 
        "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
        "git.curoverse.com/arvados.git/sdk/go/auth"
@@ -180,10 +181,19 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
                        Name:     "arvados_api_token",
                        Value:    auth.EncodeTokenCookie([]byte(t)),
                        Path:     "/",
-                       Expires:  time.Now().AddDate(10, 0, 0),
                        HttpOnly: true,
                })
-               redir := (&url.URL{Host: r.Host, Path: r.URL.Path}).String()
+
+               // Propagate query parameters (except api_token) from
+               // the original request.
+               redirQuery := r.URL.Query()
+               redirQuery.Del("api_token")
+
+               redir := (&url.URL{
+                       Host:     r.Host,
+                       Path:     r.URL.Path,
+                       RawQuery: redirQuery.Encode(),
+               }).String()
 
                w.Header().Add("Location", redir)
                statusCode, statusText = http.StatusSeeOther, redir
@@ -294,8 +304,10 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
        }
        defer rdr.Close()
 
-       // One or both of these can be -1 if not found:
        basenamePos := strings.LastIndex(filename, "/")
+       if basenamePos < 0 {
+               basenamePos = 0
+       }
        extPos := strings.LastIndex(filename, ".")
        if extPos > basenamePos {
                // Now extPos is safely >= 0.
@@ -306,13 +318,53 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
        if rdr, ok := rdr.(keepclient.ReadCloserWithLen); ok {
                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)
+       applyContentDispositionHdr(w, r, filename[basenamePos:], attachment)
+       rangeRdr, statusCode := applyRangeHdr(w, r, rdr)
+
+       w.WriteHeader(statusCode)
+       _, err = io.Copy(w, rangeRdr)
        if err != nil {
                statusCode, statusText = http.StatusBadGateway, err.Error()
        }
 }
+
+var rangeRe = regexp.MustCompile(`^bytes=0-([0-9]*)$`)
+
+func applyRangeHdr(w http.ResponseWriter, r *http.Request, rdr keepclient.ReadCloserWithLen) (io.Reader, int) {
+       w.Header().Set("Accept-Ranges", "bytes")
+       hdr := r.Header.Get("Range")
+       fields := rangeRe.FindStringSubmatch(hdr)
+       if fields == nil {
+               return rdr, http.StatusOK
+       }
+       rangeEnd, err := strconv.ParseInt(fields[1], 10, 64)
+       if err != nil {
+               // Empty or too big for int64 == send entire content
+               return rdr, http.StatusOK
+       }
+       if uint64(rangeEnd) >= rdr.Len() {
+               return rdr, http.StatusOK
+       }
+       w.Header().Set("Content-Length", fmt.Sprintf("%d", rangeEnd+1))
+       w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", 0, rangeEnd, rdr.Len()))
+       return &io.LimitedReader{R: rdr, N: rangeEnd + 1}, http.StatusPartialContent
+}
+
+func applyContentDispositionHdr(w http.ResponseWriter, r *http.Request, filename string, isAttachment bool) {
+       disposition := "inline"
+       if isAttachment {
+               disposition = "attachment"
+       }
+       if strings.ContainsRune(r.RequestURI, '?') {
+               // Help the UA realize that the filename is just
+               // "filename.txt", not
+               // "filename.txt?disposition=attachment".
+               //
+               // TODO(TC): Follow advice at RFC 6266 appendix D
+               disposition += "; filename=" + strconv.QuoteToASCII(filename)
+       }
+       if disposition != "inline" {
+               w.Header().Set("Content-Disposition", disposition)
+       }
+}