@@ -407,12 +407,8 @@ await WriteBytesAsync(isHttp10 ? s_spaceHttp10NewlineAsciiBytes : s_spaceHttp11N
407407 // And if this was 100 continue, deal with the extra headers.
408408 if ( response . StatusCode == HttpStatusCode . Continue )
409409 {
410- // We got our continue header. Read the subsequent \r\n and parse the additional status line.
411- if ( ! LineIsEmpty ( await ReadNextLineAsync ( cancellationToken ) . ConfigureAwait ( false ) ) )
412- {
413- ThrowInvalidHttpResponse ( ) ;
414- }
415-
410+ // We got our continue header. Read the subsequent empty line and parse the additional status line.
411+ await ReadEmptyLineAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
416412 ParseStatusLine ( await ReadNextLineAsync ( cancellationToken ) . ConfigureAwait ( false ) , response ) ;
417413 }
418414 }
@@ -511,8 +507,7 @@ await WriteBytesAsync(isHttp10 ? s_spaceHttp10NewlineAsciiBytes : s_spaceHttp11N
511507
512508 private static bool LineIsEmpty ( ArraySegment < byte > line )
513509 {
514- Debug . Assert ( line . Count >= 2 , "Lines should always be \r \n terminated." ) ;
515- return line . Count == 2 ;
510+ return line . Count == 0 ;
516511 }
517512
518513 private async Task SendRequestContentAsync ( Task copyTask , HttpContentWriteStream stream )
@@ -572,7 +567,7 @@ private void ParseStatusLine(Span<byte> line, HttpResponseMessage response)
572567 // We sent the request version as either 1.0 or 1.1.
573568 // We expect a response version of the form 1.X, where X is a single digit as per RFC.
574569
575- if ( line . Length < 14 || // "HTTP/1.1 123\r\n " with optional phrase before the crlf
570+ if ( line . Length < 12 || // "HTTP/1.1 123" with optional phrase
576571 line [ 0 ] != 'H' ||
577572 line [ 1 ] != 'T' ||
578573 line [ 2 ] != 'T' ||
@@ -604,39 +599,25 @@ private void ParseStatusLine(Span<byte> line, HttpResponseMessage response)
604599 ( HttpStatusCode ) ( 100 * ( status1 - '0' ) + 10 * ( status2 - '0' ) + ( status3 - '0' ) ) ;
605600
606601 // Parse (optional) reason phrase
607- byte c = line [ 12 ] ;
608- if ( c == '\r ' )
602+ if ( line . Length == 12 )
609603 {
610604 response . ReasonPhrase = string . Empty ;
611605 }
612- else if ( c != ' ' )
606+ else if ( line [ 12 ] != ' ' )
613607 {
614608 ThrowInvalidHttpResponse ( ) ;
615609 }
616610 else
617611 {
618- Span < byte > reasonBytes = line . Slice ( 13 , line . Length - 13 - 2 ) ; // 2 == \r\n ending trimmed off
612+ Span < byte > reasonBytes = line . Slice ( 13 ) ;
619613 string knownReasonPhrase = HttpStatusDescription . Get ( response . StatusCode ) ;
620614 if ( knownReasonPhrase != null && EqualsOrdinal ( knownReasonPhrase , reasonBytes ) )
621615 {
622616 response . ReasonPhrase = knownReasonPhrase ;
623617 }
624618 else
625619 {
626- unsafe
627- {
628- try
629- {
630- fixed ( byte * reasonPtr = & MemoryMarshal . GetReference ( reasonBytes ) )
631- {
632- response . ReasonPhrase = Encoding . ASCII . GetString ( reasonPtr , reasonBytes . Length ) ;
633- }
634- }
635- catch ( FormatException e )
636- {
637- ThrowInvalidHttpResponse ( e ) ;
638- }
639- }
620+ response . ReasonPhrase = Encoding . ASCII . GetString ( reasonBytes ) ;
640621 }
641622 }
642623 }
@@ -649,6 +630,8 @@ private void ParseHeaderNameValue(ArraySegment<byte> line, HttpResponseMessage r
649630
650631 private void ParseHeaderNameValue ( Span < byte > line , HttpResponseMessage response )
651632 {
633+ Debug . Assert ( line . Length > 0 ) ;
634+
652635 int pos = 0 ;
653636 while ( line [ pos ] != ( byte ) ':' && line [ pos ] != ( byte ) ' ' )
654637 {
@@ -666,8 +649,6 @@ private void ParseHeaderNameValue(Span<byte> line, HttpResponseMessage response)
666649 return ;
667650 }
668651
669- // CONSIDER: trailing whitespace?
670-
671652 if ( ! HeaderDescriptor . TryGet ( line . Slice ( 0 , pos ) , out HeaderDescriptor descriptor ) )
672653 {
673654 // Ignore invalid header name
@@ -692,12 +673,12 @@ private void ParseHeaderNameValue(Span<byte> line, HttpResponseMessage response)
692673 }
693674
694675 // Skip whitespace after colon
695- while ( pos < line . Length && ( line [ pos ] == ( byte ) ' ' || line [ pos ] == '\t ' ) )
676+ while ( pos < line . Length && ( line [ pos ] == ( byte ) ' ' || line [ pos ] == ( byte ) '\t ' ) )
696677 {
697678 pos ++ ;
698679 }
699680
700- string headerValue = descriptor . GetHeaderValue ( line . Slice ( pos , line . Length - pos - 2 ) ) ; // trim trailing \r\n
681+ string headerValue = descriptor . GetHeaderValue ( line . Slice ( pos ) ) ;
701682
702683 // Note we ignore the return value from TryAddWithoutValidation;
703684 // if the header can't be added, we silently drop it.
@@ -933,54 +914,37 @@ private Task WriteToStreamAsync(ReadOnlyMemory<byte> source, CancellationToken c
933914
934915 private async ValueTask < ArraySegment < byte > > ReadNextLineAsync ( CancellationToken cancellationToken )
935916 {
936- int searchOffset = 0 ;
917+ int previouslyScannedBytes = 0 ;
937918 while ( true )
938919 {
939- int startIndex = _readOffset + searchOffset ;
940- int length = _readLength - startIndex ;
941- int crPos = Array . IndexOf ( _readBuffer , ( byte ) '\r ' , startIndex , length ) ;
942- if ( crPos < 0 )
943- {
944- // Couldn't find a \r. Read more.
945- searchOffset = length ;
946- await FillAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
947- }
948- else if ( crPos + 1 >= _readLength )
920+ int scanOffset = _readOffset + previouslyScannedBytes ;
921+ int endIndex = Array . IndexOf ( _readBuffer , ( byte ) '\n ' , scanOffset , _readLength - scanOffset ) ;
922+ if ( endIndex >= 0 )
949923 {
950- // We found a \r, but we don't have enough data buffered to read the \n.
951- searchOffset = length - 1 ;
952- await FillAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
953- }
954- else if ( _readBuffer [ crPos + 1 ] == '\n ' )
955- {
956- // We found a \r\n. Return the data up to and including it.
957- int lineLength = crPos - _readOffset + 2 ;
958- var result = new ArraySegment < byte > ( _readBuffer , _readOffset , lineLength ) ;
959- _readOffset += lineLength ;
960- return result ;
961- }
962- else
963- {
964- ThrowInvalidHttpResponse ( ) ;
924+ int startIndex = _readOffset ;
925+ _readOffset = endIndex + 1 ;
926+
927+ if ( endIndex > startIndex && _readBuffer [ endIndex - 1 ] == '\r ' )
928+ {
929+ endIndex -- ;
930+ }
931+
932+ return new ArraySegment < byte > ( _readBuffer , startIndex , endIndex - startIndex ) ;
965933 }
966- }
967- }
968934
969- private async Task ReadCrLfAsync ( CancellationToken cancellationToken )
970- {
971- while ( _readLength - _readOffset < 2 )
972- {
973- // We have fewer than 2 chars buffered. Get more.
935+ // Couldn't find LF. Read more.
936+ // Note this may cause _readOffset to change.
937+ previouslyScannedBytes = _readLength - _readOffset ;
974938 await FillAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
975939 }
940+ }
976941
977- // We expect \r\n. If so, consume them. If not, it's an error.
978- if ( _readBuffer [ _readOffset ] != '\r ' || _readBuffer [ _readOffset + 1 ] != '\n ' )
942+ private async Task ReadEmptyLineAsync ( CancellationToken cancellationToken )
943+ {
944+ if ( ! LineIsEmpty ( await ReadNextLineAsync ( cancellationToken ) . ConfigureAwait ( false ) ) )
979945 {
980946 ThrowInvalidHttpResponse ( ) ;
981947 }
982-
983- _readOffset += 2 ;
984948 }
985949
986950 // Throws IOException on EOF. This is only called when we expect more data.
0 commit comments