1616package service
1717
1818import (
19+ "bytes"
1920 "context"
21+ "crypto/sha256"
2022 "errors"
2123 "fmt"
24+ "io"
2225 "net/http"
2326 "strconv"
2427
@@ -58,7 +61,7 @@ func (s *DownloadService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
5861 return
5962 }
6063
61- hash , err := cr_v1 .NewHash (digest )
64+ wantChecksum , err := cr_v1 .NewHash (digest )
6265 if err != nil {
6366 http .Error (w , "invalid digest" , http .StatusBadRequest )
6467 return
@@ -79,7 +82,7 @@ func (s *DownloadService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
7982 return
8083 }
8184
82- info , err := b .Describe (ctx , hash .Hex )
85+ info , err := b .Describe (ctx , wantChecksum .Hex )
8386 if err != nil && backend .IsNotFound (err ) {
8487 http .Error (w , "artifact not found" , http .StatusNotFound )
8588 return
@@ -88,20 +91,44 @@ func (s *DownloadService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
8891 return
8992 }
9093
91- // Set headers
92- w .Header ().Set ("Content-Disposition" , fmt .Sprintf ("attachment; filename=%s" , info .FileName ))
93- w .Header ().Set ("Content-Length" , strconv .FormatInt (info .Size , 10 ))
94+ s .log .Infow ("msg" , "download initialized" , "digest" , wantChecksum , "size" , bytefmt .ByteSize (uint64 (info .Size )))
9495
95- s .log .Infow ("msg" , "download initialized" , "digest" , hash , "size" , bytefmt .ByteSize (uint64 (info .Size )))
96+ gotChecksum := sha256 .New ()
97+ // create temporary buffer to write to both the writer and the checksum
98+ buf := bytes .NewBuffer (nil )
9699
97- if err := b .Download (ctx , w , hash .Hex ); err != nil {
100+ // NOTE: we don't sent the file directly to the writer because we need to calculate the checksum
101+ // and we want to send the file / even if partially only if the checksum matches
102+ // this has a performance impact but it's the only way to ensure that the file is not corrupted
103+ // and don't require client-side verification
104+ mw := io .MultiWriter (buf , gotChecksum )
105+ if err := b .Download (ctx , mw , wantChecksum .Hex ); err != nil {
98106 if errors .Is (err , context .Canceled ) {
99- s .log .Infow ("msg" , "download canceled" , "digest" , hash )
107+ s .log .Infow ("msg" , "download canceled" , "digest" , wantChecksum )
100108 return
101109 }
102110
103111 http .Error (w , sl .LogAndMaskErr (err , s .log ).Error (), http .StatusInternalServerError )
112+ return
113+ }
114+
115+ // Verify the checksum
116+ if got , want := fmt .Sprintf ("%x" , gotChecksum .Sum (nil )), wantChecksum .Hex ; got != want {
117+ msg := fmt .Sprintf ("checksums mismatch: got: %s, want: %s" , got , want )
118+ s .log .Info (msg )
119+ http .Error (w , msg , http .StatusUnauthorized )
120+ return
121+ }
122+
123+ // if the buffer contains the actual data we expect we proceed with sending it to the browser
124+ // Set headers
125+ w .Header ().Set ("Content-Disposition" , fmt .Sprintf ("attachment; filename=%s" , info .FileName ))
126+ w .Header ().Set ("Content-Length" , strconv .FormatInt (info .Size , 10 ))
127+
128+ if _ , err := io .Copy (w , buf ); err != nil {
129+ http .Error (w , sl .LogAndMaskErr (err , s .log ).Error (), http .StatusInternalServerError )
130+ return
104131 }
105132
106- s .log .Infow ("msg" , "download finished" , "digest" , hash , "size" , bytefmt .ByteSize (uint64 (info .Size )))
133+ s .log .Infow ("msg" , "download finished" , "digest" , wantChecksum , "size" , bytefmt .ByteSize (uint64 (info .Size )))
107134}
0 commit comments