1717using Google . Apis . Upload ;
1818using System ;
1919using System . IO ;
20- using System . Linq ;
2120using System . Net . Http ;
2221using System . Threading ;
2322using System . Threading . Tasks ;
@@ -73,16 +72,10 @@ public Task InterceptAsync(HttpRequestMessage request, CancellationToken cancell
7372 {
7473 return Task . CompletedTask ;
7574 }
76-
77- if ( request . Method == System . Net . Http . HttpMethod . Put && request . Content ? . Headers . Contains ( "Content-Range" ) is true )
75+ if ( _hashingStream . EndOfStreamReached )
7876 {
79- var rangeHeader = request . Content . Headers . GetValues ( "Content-Range" ) . First ( ) ;
80-
81- if ( IsFinalChunk ( rangeHeader ) )
82- {
83- var calculatedHash = _hashingStream . GetBase64Hash ( ) ;
84- request . Headers . TryAddWithoutValidation ( GoogleHashHeader , $ "crc32c={ calculatedHash } ") ;
85- }
77+ var calculatedHash = _hashingStream . GetBase64Hash ( ) ;
78+ request . Headers . TryAddWithoutValidation ( GoogleHashHeader , $ "crc32c={ calculatedHash } ") ;
8679 }
8780 return Task . CompletedTask ;
8881 }
@@ -102,51 +95,6 @@ private void OnProgressChanged(IUploadProgress progress)
10295 _mediaUpload . ProgressChanged -= OnProgressChanged ;
10396 }
10497 }
105-
106- private bool IsFinalChunk ( string rangeHeader )
107- {
108- // Expected format: "bytes {start}-{end}/{total}" or "bytes */{total}" for the final request.
109- // We are interested in the final chunk of a known-size upload.
110- const string prefix = "bytes " ;
111- if ( ! rangeHeader . StartsWith ( prefix , StringComparison . OrdinalIgnoreCase ) )
112- {
113- return false ;
114- }
115-
116- ReadOnlySpan < char > span = rangeHeader . AsSpan ( prefix . Length ) ;
117- int slashIndex = span . IndexOf ( '/' ) ;
118- if ( slashIndex == - 1 )
119- {
120- return false ;
121- }
122-
123- var totalSpan = span . Slice ( slashIndex + 1 ) ;
124- if ( totalSpan . IsEmpty || totalSpan [ 0 ] == '*' )
125- {
126- return false ;
127- }
128-
129- if ( ! long . TryParse ( totalSpan . ToString ( ) , System . Globalization . NumberStyles . Integer , System . Globalization . CultureInfo . InvariantCulture , out long totalSize ) )
130- {
131- return false ;
132- }
133-
134- var rangeSpan = span . Slice ( 0 , slashIndex ) ;
135- int dashIndex = rangeSpan . IndexOf ( '-' ) ;
136- if ( dashIndex == - 1 )
137- {
138- return false ;
139- }
140-
141- var endByteSpan = rangeSpan . Slice ( dashIndex + 1 ) ;
142- if ( ! long . TryParse ( endByteSpan . ToString ( ) , System . Globalization . NumberStyles . Integer , System . Globalization . CultureInfo . InvariantCulture , out long endByte ) )
143- {
144- return false ;
145- }
146-
147- // If endByte is the last byte of the file, it's the final chunk.
148- return ( endByte + 1 ) == totalSize ;
149- }
15098 }
15199
152100 internal sealed class HashingStream : Stream
@@ -155,6 +103,12 @@ internal sealed class HashingStream : Stream
155103 private readonly Crc32c _hasher ;
156104 private long _maxPositionHashed = 0 ;
157105
106+ ///<summary>
107+ /// Returns true if the underlying stream has returned 0 bytes,
108+ /// indicating the end of the stream has been reached.
109+ ///</summary>
110+ public bool EndOfStreamReached { get ; private set ; }
111+
158112 public HashingStream ( Stream stream )
159113 {
160114 _stream = stream ;
@@ -179,7 +133,11 @@ public override async Task<int> ReadAsync(byte[] buffer, int offset, int count,
179133
180134 private void ProcessBytes ( byte [ ] buffer , int offset , int bytesRead , long startingPos )
181135 {
182- if ( bytesRead <= 0 ) return ;
136+ if ( bytesRead <= 0 )
137+ {
138+ EndOfStreamReached = true ;
139+ return ;
140+ }
183141
184142 // Only hash bytes that are beyond the furthest point we've already hashed.
185143 // This handles the rewind and re-read scenario during retries.
0 commit comments