44using System ;
55using System . Diagnostics ;
66using System . IO ;
7- using System . Text ;
87using System . Threading ;
98using System . Threading . Tasks ;
109
1110namespace Microsoft . AspNetCore . WebUtilities
1211{
1312 internal class MultipartReaderStream : Stream
1413 {
14+ private readonly MultipartBoundary _boundary ;
1515 private readonly BufferedReadStream _innerStream ;
16- private readonly byte [ ] _boundaryBytes ;
17- private readonly int _finalBoundaryLength ;
1816 private readonly long _innerOffset ;
1917 private long _position ;
2018 private long _observedLength ;
@@ -25,8 +23,7 @@ internal class MultipartReaderStream : Stream
2523 /// </summary>
2624 /// <param name="stream">The <see cref="BufferedReadStream"/>.</param>
2725 /// <param name="boundary">The boundary pattern to use.</param>
28- /// <param name="expectLeadingCrlf">Specifies whether a leading crlf should be expected.</param>
29- public MultipartReaderStream ( BufferedReadStream stream , string boundary , bool expectLeadingCrlf = true )
26+ public MultipartReaderStream ( BufferedReadStream stream , MultipartBoundary boundary )
3027 {
3128 if ( stream == null )
3229 {
@@ -40,15 +37,7 @@ public MultipartReaderStream(BufferedReadStream stream, string boundary, bool ex
4037
4138 _innerStream = stream ;
4239 _innerOffset = _innerStream . CanSeek ? _innerStream . Position : 0 ;
43- if ( expectLeadingCrlf )
44- {
45- _boundaryBytes = Encoding . UTF8 . GetBytes ( "\r \n --" + boundary ) ;
46- }
47- else
48- {
49- _boundaryBytes = Encoding . UTF8 . GetBytes ( "--" + boundary ) ;
50- }
51- _finalBoundaryLength = _boundaryBytes . Length + 2 ; // Include the final '--' terminator.
40+ _boundary = boundary ;
5241 }
5342
5443 public bool FinalBoundaryFound { get ; private set ; }
@@ -205,7 +194,7 @@ public override int Read(byte[] buffer, int offset, int count)
205194 }
206195
207196 PositionInnerStream ( ) ;
208- if ( ! _innerStream . EnsureBuffered ( _finalBoundaryLength ) )
197+ if ( ! _innerStream . EnsureBuffered ( _boundary . FinalBoundaryLength ) )
209198 {
210199 throw new IOException ( "Unexpected end of stream." ) ;
211200 }
@@ -215,20 +204,20 @@ public override int Read(byte[] buffer, int offset, int count)
215204 int matchOffset ;
216205 int matchCount ;
217206 int read ;
218- if ( SubMatch ( bufferedData , _boundaryBytes , out matchOffset , out matchCount ) )
207+ if ( SubMatch ( bufferedData , _boundary . BoundaryBytes , out matchOffset , out matchCount ) )
219208 {
220209 // We found a possible match, return any data before it.
221210 if ( matchOffset > bufferedData . Offset )
222211 {
223212 read = _innerStream . Read ( buffer , offset , Math . Min ( count , matchOffset - bufferedData . Offset ) ) ;
224213 return UpdatePosition ( read ) ;
225214 }
226- Debug . Assert ( matchCount == _boundaryBytes . Length ) ;
215+ Debug . Assert ( matchCount == _boundary . BoundaryBytes . Length ) ;
227216
228217 // "The boundary may be followed by zero or more characters of
229218 // linear whitespace. It is then terminated by either another CRLF"
230219 // or -- for the final boundary.
231- byte [ ] boundary = new byte [ _boundaryBytes . Length ] ;
220+ byte [ ] boundary = new byte [ _boundary . BoundaryBytes . Length ] ;
232221 read = _innerStream . Read ( boundary , 0 , boundary . Length ) ;
233222 Debug . Assert ( read == boundary . Length ) ; // It should have all been buffered
234223 var remainder = _innerStream . ReadLine ( lengthLimit : 100 ) ; // Whitespace may exceed the buffer.
@@ -256,7 +245,7 @@ public override async Task<int> ReadAsync(byte[] buffer, int offset, int count,
256245 }
257246
258247 PositionInnerStream ( ) ;
259- if ( ! await _innerStream . EnsureBufferedAsync ( _finalBoundaryLength , cancellationToken ) )
248+ if ( ! await _innerStream . EnsureBufferedAsync ( _boundary . FinalBoundaryLength , cancellationToken ) )
260249 {
261250 throw new IOException ( "Unexpected end of stream." ) ;
262251 }
@@ -266,7 +255,7 @@ public override async Task<int> ReadAsync(byte[] buffer, int offset, int count,
266255 int matchOffset ;
267256 int matchCount ;
268257 int read ;
269- if ( SubMatch ( bufferedData , _boundaryBytes , out matchOffset , out matchCount ) )
258+ if ( SubMatch ( bufferedData , _boundary . BoundaryBytes , out matchOffset , out matchCount ) )
270259 {
271260 // We found a possible match, return any data before it.
272261 if ( matchOffset > bufferedData . Offset )
@@ -275,12 +264,12 @@ public override async Task<int> ReadAsync(byte[] buffer, int offset, int count,
275264 read = _innerStream . Read ( buffer , offset , Math . Min ( count , matchOffset - bufferedData . Offset ) ) ;
276265 return UpdatePosition ( read ) ;
277266 }
278- Debug . Assert ( matchCount == _boundaryBytes . Length ) ;
267+ Debug . Assert ( matchCount == _boundary . BoundaryBytes . Length ) ;
279268
280269 // "The boundary may be followed by zero or more characters of
281270 // linear whitespace. It is then terminated by either another CRLF"
282271 // or -- for the final boundary.
283- byte [ ] boundary = new byte [ _boundaryBytes . Length ] ;
272+ byte [ ] boundary = new byte [ _boundary . BoundaryBytes . Length ] ;
284273 read = _innerStream . Read ( boundary , 0 , boundary . Length ) ;
285274 Debug . Assert ( read == boundary . Length ) ; // It should have all been buffered
286275 var remainder = await _innerStream . ReadLineAsync ( lengthLimit : 100 , cancellationToken : cancellationToken ) ; // Whitespace may exceed the buffer.
@@ -300,18 +289,44 @@ public override async Task<int> ReadAsync(byte[] buffer, int offset, int count,
300289 return UpdatePosition ( read ) ;
301290 }
302291
303- // Does Segment1 contain all of segment2 , or does it end with the start of segment2 ?
292+ // Does segment1 contain all of matchBytes , or does it end with the start of matchBytes ?
304293 // 1: AAAAABBBBBCCCCC
305294 // 2: BBBBB
306295 // Or:
307296 // 1: AAAAABBB
308297 // 2: BBBBB
309- private static bool SubMatch ( ArraySegment < byte > segment1 , byte [ ] matchBytes , out int matchOffset , out int matchCount )
298+ private bool SubMatch ( ArraySegment < byte > segment1 , byte [ ] matchBytes , out int matchOffset , out int matchCount )
310299 {
300+ // clear matchCount to zero
301+ matchCount = 0 ;
302+
303+ // case 1: does segment1 fully contain matchBytes?
304+ {
305+ var matchBytesLengthMinusOne = matchBytes . Length - 1 ;
306+ var matchBytesLastByte = matchBytes [ matchBytesLengthMinusOne ] ;
307+ var segmentEndMinusMatchBytesLength = segment1 . Offset + segment1 . Count - matchBytes . Length ;
308+
309+ matchOffset = segment1 . Offset ;
310+ while ( matchOffset < segmentEndMinusMatchBytesLength )
311+ {
312+ var lookaheadTailChar = segment1 . Array [ matchOffset + matchBytesLengthMinusOne ] ;
313+ if ( lookaheadTailChar == matchBytesLastByte &&
314+ CompareBuffers ( segment1 . Array , matchOffset , matchBytes , 0 , matchBytesLengthMinusOne ) == 0 )
315+ {
316+ matchCount = matchBytes . Length ;
317+ return true ;
318+ }
319+ matchOffset += _boundary . GetSkipValue ( lookaheadTailChar ) ;
320+ }
321+ }
322+
323+ // case 2: does segment1 end with the start of matchBytes?
324+ var segmentEnd = segment1 . Offset + segment1 . Count ;
325+
311326 matchCount = 0 ;
312- for ( matchOffset = segment1 . Offset ; matchOffset < segment1 . Offset + segment1 . Count ; matchOffset ++ )
327+ for ( ; matchOffset < segmentEnd ; matchOffset ++ )
313328 {
314- int countLimit = segment1 . Offset - matchOffset + segment1 . Count ;
329+ var countLimit = segmentEnd - matchOffset ;
315330 for ( matchCount = 0 ; matchCount < matchBytes . Length && matchCount < countLimit ; matchCount ++ )
316331 {
317332 if ( matchBytes [ matchCount ] != segment1 . Array [ matchOffset + matchCount ] )
@@ -327,5 +342,17 @@ private static bool SubMatch(ArraySegment<byte> segment1, byte[] matchBytes, out
327342 }
328343 return matchCount > 0 ;
329344 }
345+
346+ private static int CompareBuffers ( byte [ ] buffer1 , int offset1 , byte [ ] buffer2 , int offset2 , int count )
347+ {
348+ for ( ; count -- > 0 ; offset1 ++ , offset2 ++ )
349+ {
350+ if ( buffer1 [ offset1 ] != buffer2 [ offset2 ] )
351+ {
352+ return buffer1 [ offset1 ] - buffer2 [ offset2 ] ;
353+ }
354+ }
355+ return 0 ;
356+ }
330357 }
331358}
0 commit comments