// Unauthenticated user. Be paranoid. func handleGetViaSharing(conn http.ResponseWriter, req *http.Request, blobRef blob.Ref, fetcher blob.StreamingFetcher) error { if !httputil.IsGet(req) { return &shareError{code: invalidMethod, response: badRequest, message: "Invalid method"} } viaPathOkay := false startTime := time.Now() defer func() { if !viaPathOkay { // Insert a delay, to hide timing attacks probing // for the existence of blobs. sleep := fetchFailureDelay - (time.Now().Sub(startTime)) time.Sleep(sleep) } }() viaBlobs := make([]blob.Ref, 0) if via := req.FormValue("via"); via != "" { for _, vs := range strings.Split(via, ",") { if br, ok := blob.Parse(vs); ok { viaBlobs = append(viaBlobs, br) } else { return &shareError{code: invalidVia, response: badRequest, message: "Malformed blobref in via param"} } } } fetchChain := make([]blob.Ref, 0) fetchChain = append(fetchChain, viaBlobs...) fetchChain = append(fetchChain, blobRef) isTransitive := false for i, br := range fetchChain { switch i { case 0: file, size, err := fetcher.FetchStreaming(br) if err != nil { return unauthorized(shareFetchFailed, "Fetch chain 0 of %s failed: %v", br, err) } defer file.Close() if size > schema.MaxSchemaBlobSize { return unauthorized(shareBlobTooLarge, "Fetch chain 0 of %s too large", br) } blob, err := schema.BlobFromReader(br, file) if err != nil { return unauthorized(shareReadFailed, "Can't create a blob from %v: %v", br, err) } share, ok := blob.AsShare() if !ok { return unauthorized(shareBlobInvalid, "Fetch chain 0 of %s wasn't a valid Share", br) } if share.IsExpired() { return unauthorized(shareExpired, "Share is expired") } if len(fetchChain) > 1 && fetchChain[1].String() != share.Target().String() { return unauthorized(shareTargetInvalid, "Fetch chain 0->1 (%s -> %q) unauthorized, expected hop to %q", br, fetchChain[1], share.Target()) } isTransitive = share.IsTransitive() if len(fetchChain) > 2 && !isTransitive { return unauthorized(shareNotTransitive, "Share is not transitive") } case len(fetchChain) - 1: // Last one is fine (as long as its path up to here has been proven, and it's // not the first thing in the chain) continue default: file, _, err := fetcher.FetchStreaming(br) if err != nil { return unauthorized(viaChainFetchFailed, "Fetch chain %d of %s failed: %v", i, br, err) } defer file.Close() lr := io.LimitReader(file, schema.MaxSchemaBlobSize) slurpBytes, err := ioutil.ReadAll(lr) if err != nil { return unauthorized(viaChainReadFailed, "Fetch chain %d of %s failed in slurp: %v", i, br, err) } saught := fetchChain[i+1].String() if bytes.Index(slurpBytes, []byte(saught)) == -1 { return unauthorized(viaChainInvalidLink, "Fetch chain %d of %s failed; no reference to %s", i, br, saught) } } } if assemble, _ := strconv.ParseBool(req.FormValue("assemble")); assemble { if !isTransitive { return unauthorized(assembleNonTransitive, "Cannot assemble non-transitive share") } dh := &DownloadHandler{ Fetcher: fetcher, // TODO(aa): It would be nice to specify a local cache here, as the UI handler does. } dh.ServeHTTP(conn, req, blobRef) } else { gethandler.ServeBlobRef(conn, req, blobRef, fetcher) } viaPathOkay = true return nil }
// Unauthenticated user. Be paranoid. func handleGetViaSharing(conn http.ResponseWriter, req *http.Request, blobRef blob.Ref, fetcher blob.StreamingFetcher) { if req.Method != "GET" && req.Method != "HEAD" { httputil.BadRequestError(conn, "Invalid method") return } viaPathOkay := false startTime := time.Now() defer func() { if !viaPathOkay { // Insert a delay, to hide timing attacks probing // for the existence of blobs. sleep := fetchFailureDelay - (time.Now().Sub(startTime)) time.Sleep(sleep) } }() viaBlobs := make([]blob.Ref, 0) if via := req.FormValue("via"); via != "" { for _, vs := range strings.Split(via, ",") { if br, ok := blob.Parse(vs); ok { viaBlobs = append(viaBlobs, br) } else { httputil.BadRequestError(conn, "Malformed blobref in via param") return } } } fetchChain := make([]blob.Ref, 0) fetchChain = append(fetchChain, viaBlobs...) fetchChain = append(fetchChain, blobRef) for i, br := range fetchChain { switch i { case 0: file, size, err := fetcher.FetchStreaming(br) if err != nil { log.Printf("Fetch chain 0 of %s failed: %v", br.String(), err) auth.SendUnauthorized(conn, req) return } defer file.Close() if size > schema.MaxSchemaBlobSize { log.Printf("Fetch chain 0 of %s too large", br.String()) auth.SendUnauthorized(conn, req) return } blob, err := schema.BlobFromReader(br, file) if err != nil { log.Printf("Can't create a blob from %v: %v", br.String(), err) auth.SendUnauthorized(conn, req) return } share, ok := blob.AsShare() if !ok { log.Printf("Fetch chain 0 of %s wasn't a valid Share", br.String()) auth.SendUnauthorized(conn, req) return } if len(fetchChain) > 1 && fetchChain[1].String() != share.Target().String() { log.Printf("Fetch chain 0->1 (%s -> %q) unauthorized, expected hop to %q", br.String(), fetchChain[1].String(), share.Target().String()) auth.SendUnauthorized(conn, req) return } case len(fetchChain) - 1: // Last one is fine (as long as its path up to here has been proven, and it's // not the first thing in the chain) continue default: file, _, err := fetcher.FetchStreaming(br) if err != nil { log.Printf("Fetch chain %d of %s failed: %v", i, br.String(), err) auth.SendUnauthorized(conn, req) return } defer file.Close() lr := io.LimitReader(file, schema.MaxSchemaBlobSize) slurpBytes, err := ioutil.ReadAll(lr) if err != nil { log.Printf("Fetch chain %d of %s failed in slurp: %v", i, br.String(), err) auth.SendUnauthorized(conn, req) return } saught := fetchChain[i+1].String() if bytes.IndexAny(slurpBytes, saught) == -1 { log.Printf("Fetch chain %d of %s failed; no reference to %s", i, br.String(), saught) auth.SendUnauthorized(conn, req) return } } } viaPathOkay = true gethandler.ServeBlobRef(conn, req, blobRef, fetcher) }