func (h *GetHandler) ServeHTTP(conn http.ResponseWriter, req *http.Request) { blobRef := blobFromUrlPath(req.URL.Path) if blobRef == nil { http.Error(conn, "Malformed GET URL.", 400) return } switch { case h.AllowGlobalAccess || auth.IsAuthorized(req): serveBlobRef(conn, req, blobRef, h.Fetcher) case auth.TriedAuthorization(req): log.Printf("Attempted authorization failed on %s", req.URL) auth.SendUnauthorized(conn) default: handleGetViaSharing(conn, req, blobRef, h.Fetcher) } }
// Unauthenticated user. Be paranoid. func handleGetViaSharing(conn http.ResponseWriter, req *http.Request, blobRef *blobref.BlobRef, fetcher blobref.StreamingFetcher) { if w, ok := fetcher.(blobserver.ContextWrapper); ok { fetcher = w.WrapContext(req) } viaPathOkay := false startTime := time.Nanoseconds() defer func() { if !viaPathOkay { // Insert a delay, to hide timing attacks probing // for the existence of blobs. sleep := fetchFailureDelayNs - (time.Nanoseconds() - startTime) if sleep > 0 { time.Sleep(sleep) } } }() viaBlobs := make([]*blobref.BlobRef, 0) if via := req.FormValue("via"); via != "" { for _, vs := range strings.Split(via, ",") { if br := blobref.Parse(vs); br == nil { httputil.BadRequestError(conn, "Malformed blobref in via param") return } else { viaBlobs = append(viaBlobs, br) } } } fetchChain := make([]*blobref.BlobRef, 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) return } defer file.Close() if size > maxJsonSize { log.Printf("Fetch chain 0 of %s too large", br.String()) auth.SendUnauthorized(conn) return } jd := json.NewDecoder(file) m := make(map[string]interface{}) if err := jd.Decode(&m); err != nil { log.Printf("Fetch chain 0 of %s wasn't JSON: %v", br.String(), err) auth.SendUnauthorized(conn) return } if m["camliType"].(string) != "share" { log.Printf("Fetch chain 0 of %s wasn't a share", br.String()) auth.SendUnauthorized(conn) return } if len(fetchChain) > 1 && fetchChain[1].String() != m["target"].(string) { log.Printf("Fetch chain 0->1 (%s -> %q) unauthorized, expected hop to %q", br.String(), fetchChain[1].String(), m["target"]) auth.SendUnauthorized(conn) 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) return } defer file.Close() lr := io.LimitReader(file, maxJsonSize) 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) 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) return } } } viaPathOkay = true serveBlobRef(conn, req, blobRef, fetcher) }