You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Ignore invalid response header in HttpClient request #29927
This looks like another case where we need to adjust SocketsHttpHandler behavior to ignore (with logging) malformed header lines and not reject the response entirely.
I can't replicate this in 4.7.2 either. It gives the following misleading error (CR is indeed followed by LF):
WebException: The server committed a protocol violation. Section=ResponseHeader Detail=CR must be followed by LF
However, Chrome, Firefox, and Curl do allow it. I think it would be reasonable (and trivial) to allow it in SocketsHttpHandler. Thoughts, @davidsh@wfurt@stephentoub?
WebException: The server committed a protocol violation. Section=ResponseHeader Detail=CR must be followed by LF
I'm curious if the parsing will work if you add the 'UseUnsafeHeaderParsing' config item. It affects .NET Framework HttpWebRequest stack and in theory will affect HttpClient. You need to put this into the app.config for .NET Framework application.
However, Chrome, Firefox, and Curl do allow it. I think it would be reasonable (and trivial) to allow it in SocketsHttpHandler. Thoughts, @davidsh@wfurt@stephentoub?
However, if this isn't a regression in .NET Core 3.0 compared with .NET Core 2.x and if this doesn't easily repro in .NET Framework without extra config settings, then this wouldn't meet the bar for a .NET Core 3.0 change.
Also, see related issue about malformed CR-LF sequences: #25319
Triage: We should create a new setting or something like that (likely not API). Bonus: Use same switch name as .NET Framework has.
It will match .NET Framework and also browsers behavior (which @scalablecory tried).
In this specific case we should either ignore the line with date without header name (likely what .NET Framework does) or use the text prior to column ':' as key and the rest as value.
Having a similar issue with a webservice where the response headers contain a header with a '=' instead of '-' in the header name.
Any possible workarounds in Core 2.X/3.0 until the useUnsafeHeaderParsing switch is implemented? Tried with both System.Net.WebRequest and System.Net.Http.HttpClient, and get the same error.
I'm trying to connect to a State Server for storing data (yes, I know it's better to use SQL Server or Redis cache, but I can't install anything on the server or create tables on the SQL Server, and I need to survive application restarts), but the state server responds with an invalid header, it doesn't send the HTTP1.1 part, but just '200 OK'. Because of that, HttpClient throws an exception:
'Received an invalid status line: '200 OK'.'
So I need a way to ignore those errors. In the meantime I use TcpClient to send and receive the data, but I rather use HttpClient.
@ChrisVanDijk that is not invalid header, that is invalid response format of the first line.
This is not something covered by this issue.
Personally I don't think it is something we should be resilient to, unless we find out it is super-wide spread.
Do you know what the server is? Is there a chance to fix it instead?
@karelz it't the good old ASP.NET State Service, according to the documentation it should respond with a standard response format, but it doesn't. Maybe that's a bug, or they did it on purpose.
I don't think this happens a lot, and I hope nobody connects to the state service outside of the old ASP.NET world. I shouldn't use it either, but for this project I don't have another option. I've fixed it by using TcpClient, which works fine for cases like this.
I have the same issue and unfortunately the server is out of my reach and cannot be fixed to return proper HTTP, so I had to go a bit lower on the network stack and send an HTTP message using TCP.
I got the original code from here but it was broken, so I had to fix it and refactored it a bit to make it cleaner and async. I didn't test the SSL code at all because I didn't need it.
I'll share the code here, but I don't claim that it's in any way production ready or safe:
//using System.Net.Security;//using System.Net.Sockets;//using System.Security.Authentication;//using System.Security.Cryptography.X509Certificates;publicstaticclassTcpClientForHttp{publicstaticTask<MemoryStream>SendHttpRequestAsync(Uriuri,HttpMethodhttpMethod,VersionhttpVersion,CancellationTokencancellationToken){stringstrHttpRequest=$"{httpMethod}{uri.PathAndQuery} HTTP/{httpVersion}\r\n";strHttpRequest+=$"Host: {uri.Host}:{uri.Port}\r\n";// Any other HTTP headers can be added here ....strHttpRequest+="\r\n";returnSendWebRequest(uri,strHttpRequest,cancellationToken);}privatestaticasyncTask<MemoryStream>SendWebRequest(Uriuri,stringrequest,CancellationTokencancellationToken){boolisHttps=uri.Scheme==Uri.UriSchemeHttps;usingvartcpClient=newTcpClient();awaittcpClient.ConnectAsync(uri.Host,uri.Port,cancellationToken);awaitusingNetworkStreamns=tcpClient.GetStream();varresultStream=newMemoryStream();if(isHttps){awaitusingvarssl=newSslStream(ns,false,ValidateServerCertificate,null);awaitssl.AuthenticateAsClientAsync(newSslClientAuthenticationOptions{TargetHost=uri.Host,ClientCertificates=null,EnabledSslProtocols=SslProtocols.None,CertificateRevocationCheckMode=X509RevocationMode.NoCheck},cancellationToken);awaitusingvarsslWriter=newStreamWriter(ssl);awaitsslWriter.WriteAsync(request);awaitsslWriter.FlushAsync();awaitssl.CopyToAsync(resultStream,cancellationToken);}else{// Normal HTTPawaitusingvarnsWriter=newStreamWriter(ns);awaitnsWriter.WriteAsync(request);awaitnsWriter.FlushAsync();awaitns.CopyToAsync(resultStream,cancellationToken);}resultStream.Position=0;returnresultStream;}privatestaticboolValidateServerCertificate(objectsender,X509Certificate?certificate,X509Chain?chain,SslPolicyErrorssslPolicyErrors){returntrue;// Accept all certs}}
Ironically, MS's own Graph API sometimes returns invalid HTTP. Instead of starting the response with a standard HTTP/1.1 blahblah string it just sends "0" (zero). And the error I get is received an invalid status line: '0'.
It'd be great if HttpClient was able to ignore/override those errors so I can look at the actual response.
Ironically, MS's own Graph API sometimes returns invalid HTTP. Instead of starting the response with a standard HTTP/1.1 blahblah string it just sends "0" (zero). And the error I get is received an invalid status line: '0'.
It'd be great if HttpClient was able to ignore/override those errors so I can look at the actual response.
P.S. This is under .NET 6
I arrived here investigating the exact same issue in a .NET 7 app. 😣 We get the same HttpRequestException: Received an invalid status line: '0'. exception message
@tsulli yes, it looks like MS Graph API has introduced some "intelligent" WAF that blocks some requests and returns invalid HTTP-message, that HttpClient cannot parse.
The problem is that if the response is structurally broken it is difficult to even start processing. That is irrelevant to who generates it and why.
The example @AnthonyMastrean provided is quire different IMHO e.g. some extra characters in header -> that is something that can be dealt with.
Ironically, MS's own Graph API sometimes returns invalid HTTP. Instead of starting the response with a standard HTTP/1.1 blahblah string it just sends "0" (zero). And the error I get is received an invalid status line: '0'.
It'd be great if HttpClient was able to ignore/override those errors so I can look at the actual response.
P.S. This is under .NET 6
Sorry for dropping in on this thread, but I've also encountered this issue. It happens sometimes when updating some emails using Graph. Did you, perhaps, find a workaround?
I've spent a couple of days trying to figure out how to work around this issue by filtering out invalid headers before ParseHeaderNameValue() attempts to parse them, but haven't figured it out. Can someone please help finish this code or share some other workaround?
SocketsHttpHandlerhandler=new();handler.PlaintextStreamFilter=(context,token)=>{// how do I remove invalid headers here? returnValueTask.FromResult(context.PlaintextStream);};HttpClientclient=new(handler);HttpResponseMessageresponse=client.GetAsync("www.google.com").Result;
I've spent a couple of days trying to figure out how to work around this issue by filtering out invalid headers before ParseHeaderNameValue() attempts to parse them, but haven't figured it out. Can someone please help finish this code or share some other workaround?
SocketsHttpHandlerhandler=new();handler.PlaintextStreamFilter=(context,token)=>{// how do I remove invalid headers here? returnValueTask.FromResult(context.PlaintextStream);};HttpClientclient=new(handler);HttpResponseMessageresponse=client.GetAsync("www.google.com").Result;
Sorry, I eventually solved the problem and finished my program, but I don't remember how I did it now. I see my code uses the NuGet packages "Selenium.WebDriver.ChromeDriver" and "HtmlAgilityPack" for something. And I see I created a separate C# console app project I named "FileDownloader" dedicated to just downloading files, which my main project calls using Process.Start() and passing in the URL to download as an argument. I think I had to use that separate project to work around the issue with the invalid headers. That FileDownloader project calls this method just before using HttpClient to download the file, but I couldn't say now whether it was the solution or just something I tried and never removed:
publicstaticboolSetAllowUnsafeHeaderParsing20(){//Get the assembly that contains the internal classAssemblyaNetAssembly=Assembly.GetAssembly(typeof(System.Net.Configuration.SettingsSection));if(aNetAssembly!=null){//Use the assembly in order to get the internal type for the internal classTypeaSettingsType=aNetAssembly.GetType("System.Net.Configuration.SettingsSectionInternal");if(aSettingsType!=null){//Use the internal static property to get an instance of the internal settings class.//If the static instance isn't created already, the property will create it for us.objectanInstance=aSettingsType.InvokeMember("Section",BindingFlags.Static|BindingFlags.GetProperty|BindingFlags.NonPublic,null,null,newobject[]{});if(anInstance!=null){//Locate the private bool field that tells the framework is unsafe header parsing should be allowed or notFieldInfoaUseUnsafeHeaderParsing=aSettingsType.GetField("useUnsafeHeaderParsing",BindingFlags.NonPublic|BindingFlags.Instance);if(aUseUnsafeHeaderParsing!=null){aUseUnsafeHeaderParsing.SetValue(anInstance,true);returntrue;}}}}returnfalse;}
useUnsafeHeaderParsing="true" is only applicable to net 4.8.1 and below
Thanks, that helped me remember my workaround better: I set up the FileDownloader project as .Net 4.8.1 specifically for that reason. The rest of my projects are .Net 7, and those just run the FileDownloader executable whenever they need to download a file that may get an invalid response header. If Microsoft fixes this problem in a newer .Net version, I can get rid of the FileDownloader project and just use HttpClient in the main project.
Hi, you need to wrap the stream with your own custom stream implementation. Something like:
SocketsHttpHandlerhandler=new();handler.PlaintextStreamFilter=(context,token)=>{// how do I remove invalid headers here? varstreamWrapper=newHttpStreamWithHeaderReplacement(context.PlaintextStream,<Replacementdetails config>);returnValueTask.FromResult(streamWrapper);};HttpClientclient=new(handler);HttpResponseMessageresponse=client.GetAsync("www.google.com").Result;
I think MS needs to really do a working example of this. You essentially need to buffer the stream ahead to get the entire header, do the replacement / transformation and then exhaust the buffer as a response, then carry on forwarding stream requests to the underlying stream.
Here's a version we have been working on in development (caveat, it needs more testing / optimisation work but should get you in the right direction):
internalclassHttpStreamWithHeaderReplacement:Stream{privatereadonlyStream_originalStream;privatereadonlyIList<IHttpStreamReplacement>_replacements;publicboolForceExit{get;privateset;}publicboolHeadersFound{get;privateset;}=false;publicintBufferOffset{get;privateset;}=0;publicHttpStreamWithHeaderReplacementPool(StreamoriginalStream,IList<IHttpStreamReplacement>replacements){_originalStream=originalStream;_replacements=replacements;}publicoverrideintRead(byte[]buffer,intoffset,intcount)=>Read(buffer.AsSpan().Slice(offset,count));publicoverrideintRead(Span<byte>buffer){while(!HeadersFound&&!ForceExit){EnsureBuffer(buffer.Length);varreadBufferSpan=_internalMemoryBuffer.Memory.Span.Slice(_usedBufferLength,buffer.Length);varchunkLength=_originalStream.Read(readBufferSpan);if(chunkLength>0){_usedBufferLength+=chunkLength;varusedBufferSpan=_internalMemoryBuffer.Memory.Span[.._usedBufferLength];vars=Encoding.ASCII.GetString(usedBufferSpan);vardoubleCRLFMatch=Regex.Match(s,@"^\s*$",RegexOptions.Multiline);// Find double CR / Empty lineif(doubleCRLFMatch.Success){// Found the headervarheaderLength=doubleCRLFMatch.Index;varheaders=s[..headerLength];varnewHeaders=headers;varhasReplacement=false;foreach(varfindReplacein_replacements){hasReplacement|=findReplace.TryReplaceContent(newHeaders,outnewHeaders);}if(hasReplacement){varremainderBufferSpan=usedBufferSpan[headerLength..];CombineAndReplaceBuffer(newHeaders,remainderBufferSpan);}HeadersFound=true;}// Completely arbitrary 20K buffer limit to stop reading forever (safety valve)if(_usedBufferLength>20000){ForceExit=true;// give up}}else{// EOFbreak;}}// Check if buffer needs to be exhaustedif(BufferOffset>=_usedBufferLength){// Buffer is empty// - Just return from the normal streamreturn_originalStream.Read(buffer);}else{// Take a chunk off the buffervaractualCount=buffer.Length;if(BufferOffset+buffer.Length>_usedBufferLength){// Need to truncate the count as its shorter than the remaining internal bufferactualCount=_usedBufferLength-BufferOffset;}varblockSpan=_internalMemoryBuffer!.Memory.Slice(BufferOffset,actualCount);blockSpan.Span.CopyTo(buffer);BufferOffset+=actualCount;returnactualCount;}}privateint_usedBufferLength=0;privateIMemoryOwner<byte>?_internalMemoryBuffer=null;[MemberNotNull(nameof(_internalMemoryBuffer))]publicvoidEnsureBuffer(intadditionalBytes){if(_internalMemoryBuffer==null){_internalMemoryBuffer=MemoryPool<byte>.Shared.Rent(additionalBytes);}elseif(_internalMemoryBuffer.Memory.Length<_usedBufferLength+additionalBytes){varnewBuffer=MemoryPool<byte>.Shared.Rent(_usedBufferLength+additionalBytes);_internalMemoryBuffer.Memory[.._usedBufferLength].CopyTo(newBuffer.Memory);ExchangeBuffer(newBuffer,_usedBufferLength);}}publicvoidCombineAndReplaceBuffer(ReadOnlySpan<char>sourceHeader,ReadOnlySpan<byte>sourceRemainingBuffer){varnewBufferLength=sourceHeader.Length+sourceRemainingBuffer.Length;varreplacedBuffer=MemoryPool<byte>.Shared.Rent(newBufferLength);// Copy the new Header Bytes into the new BuffervarnewHeaderLength=Encoding.ASCII.GetBytes(sourceHeader,replacedBuffer.Memory.Span);if(sourceRemainingBuffer.Length>0){// Copy the new the remainder Bytes into the new BuffervartargetRemainderSpan=replacedBuffer.Memory.Span[newHeaderLength..];sourceRemainingBuffer.CopyTo(targetRemainderSpan);}ExchangeBuffer(replacedBuffer,newBufferLength);}publicvoidExchangeBuffer(IMemoryOwner<byte>newBuffer,intusedBufferLength){_internalMemoryBuffer?.Dispose();_internalMemoryBuffer=newBuffer;_usedBufferLength=usedBufferLength;}protectedoverridevoidDispose(booldisposing){_internalMemoryBuffer?.Dispose();_internalMemoryBuffer=null;_originalStream?.Dispose();base.Dispose(disposing);}publicoverrideboolCanRead=>_originalStream.CanRead;publicoverrideboolCanSeek=>false;publicoverrideboolCanWrite=>_originalStream.CanWrite;publicoverridelongLength=>_originalStream.Length;publicoverridelongPosition{get=>_originalStream.Position;set=>thrownewNotSupportedException();}publicoverridevoidFlush(){_originalStream.Flush();}publicoverridelongSeek(longoffset,SeekOriginorigin){thrownewNotSupportedException();}publicoverridevoidSetLength(longvalue){thrownewNotSupportedException();}publicoverridevoidWrite(byte[]buffer,intoffset,intcount){_originalStream.Write(buffer,offset,count);}}
The IHttpStreamReplacement was declared like this, so you can implement your own version of this to do what you want:
Activity
davidsh commentedon Jun 21, 2019
cc: @dotnet/ncl @stephentoub
This looks like another case where we need to adjust SocketsHttpHandler behavior to ignore (with logging) malformed header lines and not reject the response entirely.
scalablecory commentedon Jun 22, 2019
I've just tested PlatformHandler/WinHTTP, it does not allow this. Will check Framework next.
scalablecory commentedon Jun 22, 2019
I can't replicate this in 4.7.2 either. It gives the following misleading error (CR is indeed followed by LF):
However, Chrome, Firefox, and Curl do allow it. I think it would be reasonable (and trivial) to allow it in
SocketsHttpHandler. Thoughts, @davidsh @wfurt @stephentoub?davidsh commentedon Jun 22, 2019
I'm curious if the parsing will work if you add the 'UseUnsafeHeaderParsing' config item. It affects .NET Framework HttpWebRequest stack and in theory will affect HttpClient. You need to put this into the app.config for .NET Framework application.
https://docs.microsoft.com/en-us/dotnet/api/system.net.configuration.httpwebrequestelement.useunsafeheaderparsing?view=netframework-4.8
However, if this isn't a regression in .NET Core 3.0 compared with .NET Core 2.x and if this doesn't easily repro in .NET Framework without extra config settings, then this wouldn't meet the bar for a .NET Core 3.0 change.
Also, see related issue about malformed CR-LF sequences: #25319
scalablecory commentedon Jun 27, 2019
Works with useUnsafeHeaderParsing="true".
Does not work on 2.2. Moving to Future.
[-]unstandardized response header in HttpClient request[/-][+]Ignore invalid response header in HttpClient request[/+]karelz commentedon Oct 8, 2019
Triage: We should create a new setting or something like that (likely not API). Bonus: Use same switch name as .NET Framework has.
It will match .NET Framework and also browsers behavior (which @scalablecory tried).
In this specific case we should either ignore the line with date without header name (likely what .NET Framework does) or use the text prior to column ':' as key and the rest as value.
FullstackSensei commentedon Oct 23, 2019
Having a similar issue with a webservice where the response headers contain a header with a '=' instead of '-' in the header name.
Any possible workarounds in Core 2.X/3.0 until the useUnsafeHeaderParsing switch is implemented? Tried with both System.Net.WebRequest and System.Net.Http.HttpClient, and get the same error.
ChrisVanDijk commentedon Mar 2, 2020
I'm trying to connect to a State Server for storing data (yes, I know it's better to use SQL Server or Redis cache, but I can't install anything on the server or create tables on the SQL Server, and I need to survive application restarts), but the state server responds with an invalid header, it doesn't send the HTTP1.1 part, but just '200 OK'. Because of that, HttpClient throws an exception:
'Received an invalid status line: '200 OK'.'
So I need a way to ignore those errors. In the meantime I use TcpClient to send and receive the data, but I rather use HttpClient.
karelz commentedon Mar 2, 2020
@ChrisVanDijk that is not invalid header, that is invalid response format of the first line.
This is not something covered by this issue.
Personally I don't think it is something we should be resilient to, unless we find out it is super-wide spread.
Do you know what the server is? Is there a chance to fix it instead?
ChrisVanDijk commentedon Mar 3, 2020
@karelz it't the good old ASP.NET State Service, according to the documentation it should respond with a standard response format, but it doesn't. Maybe that's a bug, or they did it on purpose.
I don't think this happens a lot, and I hope nobody connects to the state service outside of the old ASP.NET world. I shouldn't use it either, but for this project I don't have another option. I've fixed it by using TcpClient, which works fine for cases like this.
14 remaining items
sherman89 commentedon May 20, 2022
I have the same issue and unfortunately the server is out of my reach and cannot be fixed to return proper HTTP, so I had to go a bit lower on the network stack and send an HTTP message using TCP.
I got the original code from here but it was broken, so I had to fix it and refactored it a bit to make it cleaner and async. I didn't test the SSL code at all because I didn't need it.
I'll share the code here, but I don't claim that it's in any way production ready or safe:
alex-jitbit commentedon Feb 27, 2023
Ironically, MS's own Graph API sometimes returns invalid HTTP. Instead of starting the response with a standard
HTTP/1.1 blahblahstring it just sends "0" (zero). And the error I get isreceived an invalid status line: '0'.It'd be great if HttpClient was able to ignore/override those errors so I can look at the actual response.
P.S. This is under .NET 6
tsulli commentedon Feb 28, 2023
I arrived here investigating the exact same issue in a .NET 7 app. 😣 We get the same
HttpRequestException: Received an invalid status line: '0'.exception messagealex-jitbit commentedon Feb 28, 2023
@tsulli yes, it looks like MS Graph API has introduced some "intelligent" WAF that blocks some requests and returns invalid HTTP-message, that
HttpClientcannot parse.P.S. (offtopic) Theresa, FWIW we were able to work this around by using request-batching https://learn.microsoft.com/en-us/graph/json-batching?context=graph%2Fapi%2F1.0&view=graph-rest-1.0
tsulli commentedon Feb 28, 2023
@alex-jitbit We'll give that a shot, thanks for the tip!
wfurt commentedon Feb 28, 2023
The problem is that if the response is structurally broken it is difficult to even start processing. That is irrelevant to who generates it and why.
The example @AnthonyMastrean provided is quire different IMHO e.g. some extra characters in header -> that is something that can be dealt with.
Vixan commentedon Mar 10, 2023
Sorry for dropping in on this thread, but I've also encountered this issue. It happens sometimes when updating some emails using Graph. Did you, perhaps, find a workaround?
wfurt commentedon Mar 10, 2023
retries? It seems to me that the right move is the push and fix
Graph APIto produce valid HTTP.alex-jitbit commentedon Mar 10, 2023
@Vixan yes, found a workaorund, mentioned here: #29927 (comment)
Germs2004 commentedon Mar 16, 2023
I've spent a couple of days trying to figure out how to work around this issue by filtering out invalid headers before ParseHeaderNameValue() attempts to parse them, but haven't figured it out. Can someone please help finish this code or share some other workaround?
AmberHan commentedon Jul 13, 2023
Did you solve this problem?
Germs2004 commentedon Jul 14, 2023
Sorry, I eventually solved the problem and finished my program, but I don't remember how I did it now. I see my code uses the NuGet packages "Selenium.WebDriver.ChromeDriver" and "HtmlAgilityPack" for something. And I see I created a separate C# console app project I named "FileDownloader" dedicated to just downloading files, which my main project calls using Process.Start() and passing in the URL to download as an argument. I think I had to use that separate project to work around the issue with the invalid headers. That FileDownloader project calls this method just before using HttpClient to download the file, but I couldn't say now whether it was the solution or just something I tried and never removed:
AmberHan commentedon Jul 14, 2023
Thank you very much for your answer, but useUnsafeHeaderParsing="true" is only applicable to net 4.8.1 and below, and I finally use Tcp to download.
Germs2004 commentedon Jul 14, 2023
Thanks, that helped me remember my workaround better: I set up the FileDownloader project as .Net 4.8.1 specifically for that reason. The rest of my projects are .Net 7, and those just run the FileDownloader executable whenever they need to download a file that may get an invalid response header. If Microsoft fixes this problem in a newer .Net version, I can get rid of the FileDownloader project and just use HttpClient in the main project.
Simon-Gregory-LG commentedon Jul 14, 2023
Hi, you need to wrap the stream with your own custom stream implementation. Something like:
I think MS needs to really do a working example of this. You essentially need to buffer the stream ahead to get the entire header, do the replacement / transformation and then exhaust the buffer as a response, then carry on forwarding stream requests to the underlying stream.
Here's a version we have been working on in development (caveat, it needs more testing / optimisation work but should get you in the right direction):
The IHttpStreamReplacement was declared like this, so you can implement your own version of this to do what you want: