@@ -167,7 +167,27 @@ public override int Read(byte[] buffer, int offset, int count)
167
167
}
168
168
var bufferedData = _innerStream . BufferedData ;
169
169
170
- // scan for a boundary match, full or partial.
170
+ var index = bufferedData . AsSpan ( ) . IndexOf ( _boundary . BoundaryBytes ) ;
171
+ if ( index >= 0 )
172
+ {
173
+ // There is data before the boundary, we should return it to the user
174
+ if ( index != 0 )
175
+ {
176
+ // Sync, it's already buffered
177
+ var slice = buffer . AsSpan ( 0 , Math . Min ( buffer . Length , index ) ) ;
178
+
179
+ var readAmount = _innerStream . Read ( slice ) ;
180
+ return UpdatePosition ( readAmount ) ;
181
+ }
182
+ else
183
+ {
184
+ var length = _boundary . BoundaryBytes . Length ;
185
+
186
+ return ReadBoundary ( this , length ) ;
187
+ }
188
+ }
189
+
190
+ // scan for a partial boundary match.
171
191
int read ;
172
192
if ( SubMatch ( bufferedData , _boundary . BoundaryBytes , out var matchOffset , out var matchCount ) )
173
193
{
@@ -181,28 +201,33 @@ public override int Read(byte[] buffer, int offset, int count)
181
201
var length = _boundary . BoundaryBytes . Length ;
182
202
Debug . Assert ( matchCount == length ) ;
183
203
204
+ return ReadBoundary ( this , length ) ;
205
+ }
206
+
207
+ // No possible boundary match within the buffered data, return the data from the buffer.
208
+ read = _innerStream . Read ( buffer , offset , Math . Min ( count , bufferedData . Count ) ) ;
209
+ return UpdatePosition ( read ) ;
210
+
211
+ static int ReadBoundary ( MultipartReaderStream stream , int length )
212
+ {
184
213
// "The boundary may be followed by zero or more characters of
185
214
// linear whitespace. It is then terminated by either another CRLF"
186
215
// or -- for the final boundary.
187
- var boundary = _bytePool . Rent ( length ) ;
188
- read = _innerStream . Read ( boundary , 0 , length ) ;
189
- _bytePool . Return ( boundary ) ;
216
+ var boundary = stream . _bytePool . Rent ( length ) ;
217
+ var read = stream . _innerStream . Read ( boundary , 0 , length ) ;
218
+ stream . _bytePool . Return ( boundary ) ;
190
219
Debug . Assert ( read == length ) ; // It should have all been buffered
191
220
192
- var remainder = _innerStream . ReadLine ( lengthLimit : 100 ) ; // Whitespace may exceed the buffer.
221
+ var remainder = stream . _innerStream . ReadLine ( lengthLimit : 100 ) . AsSpan ( ) ; // Whitespace may exceed the buffer.
193
222
remainder = remainder . Trim ( ) ;
194
- if ( string . Equals ( "--" , remainder , StringComparison . Ordinal ) )
223
+ if ( remainder . Equals ( "--" , StringComparison . Ordinal ) )
195
224
{
196
- FinalBoundaryFound = true ;
225
+ stream . FinalBoundaryFound = true ;
197
226
}
198
- Debug . Assert ( FinalBoundaryFound || string . Equals ( string . Empty , remainder , StringComparison . Ordinal ) , "Un-expected data found on the boundary line: " + remainder ) ;
199
- _finished = true ;
227
+ Debug . Assert ( stream . FinalBoundaryFound || remainder . IsEmpty , "Un-expected data found on the boundary line: " + remainder . ToString ( ) ) ;
228
+ stream . _finished = true ;
200
229
return 0 ;
201
230
}
202
-
203
- // No possible boundary match within the buffered data, return the data from the buffer.
204
- read = _innerStream . Read ( buffer , offset , Math . Min ( count , bufferedData . Count ) ) ;
205
- return UpdatePosition ( read ) ;
206
231
}
207
232
208
233
public override Task < int > ReadAsync ( byte [ ] buffer , int offset , int count , CancellationToken cancellationToken )
@@ -222,6 +247,27 @@ public override async ValueTask<int> ReadAsync(Memory<byte> buffer, Cancellation
222
247
}
223
248
var bufferedData = _innerStream . BufferedData ;
224
249
250
+ var index = bufferedData . AsSpan ( ) . IndexOf ( _boundary . BoundaryBytes ) ;
251
+
252
+ if ( index >= 0 )
253
+ {
254
+ // There is data before the boundary, we should return it to the user
255
+ if ( index != 0 )
256
+ {
257
+ var slice = buffer [ ..Math . Min ( buffer . Length , index ) ] ;
258
+
259
+ // Sync, it's already buffered
260
+ var readAmount = _innerStream . Read ( slice . Span ) ;
261
+ return UpdatePosition ( readAmount ) ;
262
+ }
263
+ else
264
+ {
265
+ var length = _boundary . BoundaryBytes . Length ;
266
+
267
+ return await ReadBoundaryAsync ( this , length , cancellationToken ) ;
268
+ }
269
+ }
270
+
225
271
// scan for a boundary match, full or partial.
226
272
int matchOffset ;
227
273
int matchCount ;
@@ -231,70 +277,52 @@ public override async ValueTask<int> ReadAsync(Memory<byte> buffer, Cancellation
231
277
// We found a possible match, return any data before it.
232
278
if ( matchOffset > bufferedData . Offset )
233
279
{
234
- // Sync, it's already buffered
235
280
var slice = buffer [ ..Math . Min ( buffer . Length , matchOffset - bufferedData . Offset ) ] ;
236
281
282
+ // Sync, it's already buffered
237
283
read = _innerStream . Read ( slice . Span ) ;
238
284
return UpdatePosition ( read ) ;
239
285
}
240
286
241
- var length = _boundary . BoundaryBytes ! . Length ;
287
+ var length = _boundary . BoundaryBytes . Length ;
242
288
Debug . Assert ( matchCount == length ) ;
243
289
290
+ return await ReadBoundaryAsync ( this , length , cancellationToken ) ;
291
+ }
292
+
293
+ // No possible boundary match within the buffered data, return the data from the buffer.
294
+ read = _innerStream . Read ( buffer . Span [ ..Math . Min ( buffer . Length , bufferedData . Count ) ] ) ;
295
+ return UpdatePosition ( read ) ;
296
+
297
+ static async Task < int > ReadBoundaryAsync ( MultipartReaderStream stream , int length , CancellationToken cancellationToken )
298
+ {
244
299
// "The boundary may be followed by zero or more characters of
245
300
// linear whitespace. It is then terminated by either another CRLF"
246
301
// or -- for the final boundary.
247
- var boundary = _bytePool . Rent ( length ) ;
248
- read = _innerStream . Read ( boundary , 0 , length ) ;
249
- _bytePool . Return ( boundary ) ;
302
+ var boundary = stream . _bytePool . Rent ( length ) ;
303
+ var read = stream . _innerStream . Read ( boundary , 0 , length ) ;
304
+ stream . _bytePool . Return ( boundary ) ;
250
305
Debug . Assert ( read == length ) ; // It should have all been buffered
251
306
252
- var remainder = await _innerStream . ReadLineAsync ( lengthLimit : 100 , cancellationToken : cancellationToken ) ; // Whitespace may exceed the buffer.
307
+ var remainder = await stream . _innerStream . ReadLineAsync ( lengthLimit : 100 , cancellationToken : cancellationToken ) ; // Whitespace may exceed the buffer.
253
308
remainder = remainder . Trim ( ) ;
254
309
if ( string . Equals ( "--" , remainder , StringComparison . Ordinal ) )
255
310
{
256
- FinalBoundaryFound = true ;
311
+ stream . FinalBoundaryFound = true ;
257
312
}
258
- Debug . Assert ( FinalBoundaryFound || string . Equals ( string . Empty , remainder , StringComparison . Ordinal ) , "Un-expected data found on the boundary line: " + remainder ) ;
313
+ Debug . Assert ( stream . FinalBoundaryFound || string . Equals ( string . Empty , remainder , StringComparison . Ordinal ) , "Un-expected data found on the boundary line: " + remainder ) ;
259
314
260
- _finished = true ;
315
+ stream . _finished = true ;
261
316
return 0 ;
262
317
}
263
-
264
- // No possible boundary match within the buffered data, return the data from the buffer.
265
- read = _innerStream . Read ( buffer . Span [ ..Math . Min ( buffer . Length , bufferedData . Count ) ] ) ;
266
- return UpdatePosition ( read ) ;
267
318
}
268
319
269
- // Does segment1 contain all of matchBytes, or does it end with the start of matchBytes?
270
- // 1: AAAAABBBBBCCCCC
271
- // 2: BBBBB
272
- // Or:
320
+ // Does segment1 end with the start of matchBytes?
273
321
// 1: AAAAABBB
274
322
// 2: BBBBB
275
- private bool SubMatch ( ArraySegment < byte > segment1 , byte [ ] matchBytes , out int matchOffset , out int matchCount )
323
+ private static bool SubMatch ( ArraySegment < byte > segment1 , ReadOnlySpan < byte > matchBytes , out int matchOffset , out int matchCount )
276
324
{
277
- // case 1: does segment1 fully contain matchBytes?
278
- {
279
- var matchBytesLengthMinusOne = matchBytes . Length - 1 ;
280
- var matchBytesLastByte = matchBytes [ matchBytesLengthMinusOne ] ;
281
- var segmentEndMinusMatchBytesLength = segment1 . Offset + segment1 . Count - matchBytes . Length ;
282
-
283
- matchOffset = segment1 . Offset ;
284
- while ( matchOffset < segmentEndMinusMatchBytesLength )
285
- {
286
- var lookaheadTailChar = segment1 . Array ! [ matchOffset + matchBytesLengthMinusOne ] ;
287
- if ( lookaheadTailChar == matchBytesLastByte &&
288
- CompareBuffers ( segment1 . Array , matchOffset , matchBytes , 0 , matchBytesLengthMinusOne ) == 0 )
289
- {
290
- matchCount = matchBytes . Length ;
291
- return true ;
292
- }
293
- matchOffset += _boundary . GetSkipValue ( lookaheadTailChar ) ;
294
- }
295
- }
296
-
297
- // case 2: does segment1 end with the start of matchBytes?
325
+ matchOffset = Math . Max ( segment1 . Offset , segment1 . Offset + segment1 . Count - matchBytes . Length ) ;
298
326
var segmentEnd = segment1 . Offset + segment1 . Count ;
299
327
300
328
// clear matchCount to zero
@@ -315,19 +343,8 @@ private bool SubMatch(ArraySegment<byte> segment1, byte[] matchBytes, out int ma
315
343
break ;
316
344
}
317
345
}
318
- return matchCount > 0 ;
319
- }
320
346
321
- private static int CompareBuffers ( byte [ ] buffer1 , int offset1 , byte [ ] buffer2 , int offset2 , int count )
322
- {
323
- for ( ; count -- > 0 ; offset1 ++ , offset2 ++ )
324
- {
325
- if ( buffer1 [ offset1 ] != buffer2 [ offset2 ] )
326
- {
327
- return buffer1 [ offset1 ] - buffer2 [ offset2 ] ;
328
- }
329
- }
330
- return 0 ;
347
+ return matchCount > 0 ;
331
348
}
332
349
333
350
public override void CopyTo ( Stream destination , int bufferSize )
0 commit comments