@@ -484,11 +484,21 @@ private HttpClient.ResponseWrapper<String> headBlob(ContainerRef containerRef) {
484484 */
485485 @ Override
486486 public byte [] getBlob (ContainerRef containerRef ) {
487- try (InputStream is = fetchBlob (containerRef )) {
488- return ensureDigest (containerRef , is .readAllBytes ());
489- } catch (IOException e ) {
490- throw new OrasException ("Failed to get blob" , e );
487+ if (!hasBlob (containerRef )) {
488+ throw new OrasException (new HttpClient .ResponseWrapper <>("" , 404 , Map .of ()));
491489 }
490+ URI uri = URI .create (
491+ "%s://%s" .formatted (getScheme (), containerRef .forRegistry (this ).getBlobsPath (this )));
492+ HttpClient .ResponseWrapper <String > response = client .get (
493+ uri ,
494+ Map .of (Const .ACCEPT_HEADER , Const .APPLICATION_OCTET_STREAM_HEADER_VALUE ),
495+ Scopes .of (this , containerRef ),
496+ authProvider );
497+ logResponse (response );
498+ handleError (response );
499+ byte [] data = response .response ().getBytes (StandardCharsets .UTF_8 );
500+ validateDockerContentDigest (response , data );
501+ return data ;
492502 }
493503
494504 @ Override
@@ -506,6 +516,7 @@ public void fetchBlob(ContainerRef containerRef, Path path) {
506516 authProvider );
507517 logResponse (response );
508518 handleError (response );
519+ validateDockerContentDigest (response , path );
509520 }
510521
511522 @ Override
@@ -522,6 +533,7 @@ public InputStream fetchBlob(ContainerRef containerRef) {
522533 authProvider );
523534 logResponse (response );
524535 handleError (response );
536+ validateDockerContentDigest (response );
525537 return response .response ();
526538 }
527539
@@ -530,8 +542,8 @@ public Descriptor fetchBlobDescriptor(ContainerRef containerRef) {
530542 HttpClient .ResponseWrapper <String > response = headBlob (containerRef );
531543 handleError (response );
532544 String size = response .headers ().get (Const .CONTENT_LENGTH_HEADER .toLowerCase ());
533- String digest = response . headers (). get ( Const . DOCKER_CONTENT_DIGEST_HEADER . toLowerCase ());
534- return Descriptor . of ( digest , Long .parseLong (size ), Const .DEFAULT_DESCRIPTOR_MEDIA_TYPE );
545+ return Descriptor . of (
546+ validateDockerContentDigest ( response ) , Long .parseLong (size ), Const .DEFAULT_DESCRIPTOR_MEDIA_TYPE );
535547 }
536548
537549 @ Override
@@ -562,15 +574,16 @@ public Descriptor getDescriptor(ContainerRef containerRef) {
562574 HttpClient .ResponseWrapper <String > response = getManifestResponse (containerRef );
563575 handleError (response );
564576 String size = response .headers ().get (Const .CONTENT_LENGTH_HEADER .toLowerCase ());
565- String digest = response .headers ().get (Const .DOCKER_CONTENT_DIGEST_HEADER .toLowerCase ());
566577 String contentType = response .headers ().get (Const .CONTENT_TYPE_HEADER .toLowerCase ());
567- return Descriptor .of (digest , Long .parseLong (size ), contentType ).withJson (response .response ());
578+ return Descriptor .of (validateDockerContentDigest (response ), Long .parseLong (size ), contentType )
579+ .withJson (response .response ());
568580 }
569581
570582 @ Override
571583 public Descriptor probeDescriptor (ContainerRef ref ) {
572584 Map <String , String > headers = getHeaders (ref );
573- String digest = headers .get (Const .DOCKER_CONTENT_DIGEST_HEADER .toLowerCase ());
585+ String digest = validateDockerContentDigest (headers );
586+ SupportedAlgorithm .fromDigest (digest );
574587 String contentType = headers .get (Const .CONTENT_TYPE_HEADER .toLowerCase ());
575588 return Descriptor .of (digest , 0L , contentType );
576589 }
@@ -594,16 +607,58 @@ private HttpClient.ResponseWrapper<String> getManifestResponse(ContainerRef cont
594607 uri , Map .of ("Accept" , Const .MANIFEST_ACCEPT_TYPE ), Scopes .of (this , containerRef ), authProvider );
595608 }
596609
597- private byte [] ensureDigest (ContainerRef ref , byte [] data ) {
610+ private void validateDockerContentDigest (HttpClient .ResponseWrapper <String > response , byte [] data ) {
611+ String digest = response .headers ().get (Const .DOCKER_CONTENT_DIGEST_HEADER .toLowerCase ());
612+ // This might happen when blob are hosted other storage.
613+ // We need a way to propagate the headers like scoped.
614+ // For now just skip validation
615+ if (digest == null ) {
616+ LOG .warn ("Docker-Content-Digest header not found in response. Skipping validation." );
617+ return ;
618+ }
619+ String computedDigest = SupportedAlgorithm .fromDigest (digest ).digest (data );
620+ ensureDigest (digest , computedDigest );
621+ }
622+
623+ private void validateDockerContentDigest (HttpClient .ResponseWrapper <Path > response , Path path ) {
624+ String digest = response .headers ().get (Const .DOCKER_CONTENT_DIGEST_HEADER .toLowerCase ());
625+ // This might happen when blob are hosted other storage.
626+ // We need a way to propagate the headers like scoped.
627+ // For now just skip validation
628+ if (digest == null ) {
629+ LOG .warn ("Docker-Content-Digest header not found in response. Skipping validation." );
630+ return ;
631+ }
632+ String computedDigest = SupportedAlgorithm .fromDigest (digest ).digest (path );
633+ ensureDigest (digest , computedDigest );
634+ }
635+
636+ private String validateDockerContentDigest (HttpClient .ResponseWrapper <?> response ) {
637+ return validateDockerContentDigest (response .headers ());
638+ }
639+
640+ private String validateDockerContentDigest (Map <String , String > headers ) {
641+ String digest = headers .get (Const .DOCKER_CONTENT_DIGEST_HEADER .toLowerCase ());
642+ SupportedAlgorithm .fromDigest (digest );
643+ return digest ;
644+ }
645+
646+ private void ensureDigest (ContainerRef ref , byte [] data ) {
598647 if (ref .getDigest () == null ) {
599648 throw new OrasException ("Missing digest" );
600649 }
601650 SupportedAlgorithm algorithm = SupportedAlgorithm .fromDigest (ref .getDigest ());
602651 String dataDigest = algorithm .digest (data );
603- if (!ref .getDigest ().equals (dataDigest )) {
604- throw new OrasException ("Digest mismatch: %s != %s" .formatted (ref .getDigest (), dataDigest ));
652+ ensureDigest (ref .getDigest (), dataDigest );
653+ }
654+
655+ private void ensureDigest (String expected , @ Nullable String current ) {
656+ if (current == null ) {
657+ throw new OrasException ("Received null digest" );
658+ }
659+ if (!expected .equals (current )) {
660+ throw new OrasException ("Digest mismatch: %s != %s" .formatted (expected , current ));
605661 }
606- return data ;
607662 }
608663
609664 /**
0 commit comments