HttpClient is modifying my original request to change domains - c#

I have some code that is making a Server2Server call using an HttpClient. Here is some quick code
Code to make a request
private HttpRequestMessage GetRequest(Uri uri, NameValueCollection headers)
{
var method = HttpMethod.Get;
var request = new HttpRequestMessage(method, uri);
foreach (string v in headers)
{
var success = request.Headers.TryAddWithoutValidation(v, headers[v]);
if (!success)
{
// log something ( no logs are appearing )
}
}
return request;
}
Code to make the request
private void AsyncCallUrl(Uri url, NameValueCollection headers)
{
object result1 = null;
var handler = new HttpClientHandler() { AllowAutoRedirect = false };
using (HttpClient client = new HttpClient(handler))
{
var request = GetRequest(url, headers);
using (HttpResponseMessage response = client.SendAsync(request).Result) // when this line is executed the request object's domain is changed to something else
{
using (HttpContent content = response.Content)
{
result1 = content.ReadAsStringAsync().Result;
}
}
}
I've verified that the request object is created appropriately with the proper domain. I've also verified that the network traffic is going to the wrong domain, even the request object shows the new bad domain. What I don't is why this is happening. I've even set the AllowAutoRedirect to false
NOTE: As a note I notice that if I use GetAsync instead of SendAsync the domain change doesn't happen. However this is not an ideal solution as in order to add headers I would need to add them to the HttpClient itself and this code lives on an IIS server and I don't want to make a new client for every request

So, with SendAsync the value of the Host header of the request is determined by the uri parameter... However it is possible to override the Host header through the Headers property of the request.
It's highly likely that the NameValueCollection headers that you are blindly injecting into the request's headers contains an entry for Host which is different to that which you supplied in the Uri.
As an aside, this behaviour can be useful, if (for instance) you were to discover that the DNS performance of HttpWebRequest (the business end of HttpClient on Windows) is sub-standard. I've bypassed .Net/Windows DNS by using a third party library to look up the IP of the host, rewriting the Uri to use the IP address in place of the host name, then setting the Host header on the outgoing request back to the original host name.

Related

How to set content-md5 header in GET method using HttpClient?

I have the following code to set content-md5 in my GET method request using HttpClient
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("content-md5", "value");
I cannot use HttpRequestMessage content to set it because it's not a POST method. When using Postman it works like a charm but fails when using HttpClient.GetAsync.
Client request a hmac to the server as follows
{
"content_to_hash": "my content"
}
The server will give response like this
{
"content_md5": "88af7ceab9fdafb76xxxxx",
"date": "Sat, 02 May 2020 00:13:16 +0700",
"hmac_value": "WfHgFyT792IENmK8Mqz9LysmP8ftOP00qA="
}
Now I have to access a GET request using that hmac where it's the problem because I cannot set in httpClient GET request header.
Here's the image
From reading the HttpClient and related source code, there's no way you can get around this and add the header to the actual request object headers. There is an internal list of invalid headers, which includes any Content-* headers. It has to be on a content object.
Therefore, my suggest solution is to create your own content object:
public class NoContentMd5 : HttpContent
{
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
return Task.CompletedTask;
}
protected override bool TryComputeLength(out long length)
{
length = 0;
return false;
}
public NoContentMd5(byte[] contentMd5)
{
this.Headers.ContentMD5 = contentMd5;
}
public NoContentMd5(string contentMd5)
{
this.Headers.TryAddWithoutValidation("Content-MD5", contentMd5);
}
}
This will add the Content-MD5 header with a value of your choosing, but the request won't contain a body.
The next problem you'll encounter is that you're trying to make a GET request with content, which isn't supported by the helper client.GetAsync(...) method. You'll have to make your own request object and use client.SendAsync(...) instead:
HttpClient client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost/my/test/uri");
request.Content = new NoContentMd5("d41d8cd98f00b204e9800998ecf8427e ");
var result = await client.SendAsync(request);
Note that if you have your Content-MD5 hash as bytes, I've also added a constructor to NoContentMd5 for byte[] too.
The only potential issue with this is that it includes a Content-Length: 0 header. Hopefully that's OK with the API you're working with.
There's an alternative solution described in this answer to question with a similar issue. I'd argue against using it since is vulnerable to changes in the implementation details of HttpRequestHeaders (because it uses reflection, so if MS change the code, it might break) .
Aside from the fact that it's not considered good practice to send a body with GET request (see HTTP GET with request body), you can try this:
using (var content = new StringContent(string.Empty))
using (var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("http://localhost"),
Content = content
})
{
request.Headers.TryAddWithoutValidation("content-md5", "value");;
using (var response = await httpClient.SendAsync(request))
{
response.EnsureSuccessStatusCode();
}
}
UPDATE:
The proper way would be to set the ContentMD5 property on the HttpContentHeaders, for example:
content.Headers.ContentMD5 = Convert.FromBase64String(hashString);
But as you pointed out in the comments, trying to send content in a GET request causes an error.

How to execute two dependent odata url's in same session from C# Code

I need to call two odata URL's of sap from C# Code.
The first URL will set the Filters and the second URL (generic URL) will give the actual response.
First URL:
https://<>/sap/opu/odata/SALM/TM_SERVICE/setFilters?sap-language=EN&filter='2%2Ctime%3D%E2%80%8E4%E2%80%8E%2F%E2%80%8E1%E2%80%8E%2F%E2%80%8E2019%E2%80%8E%20%E2%80%8E10%E2%80%8E%3A%E2%80%8E31%E2%80%8E%3A%E2%80%8E24%E2%80%8E%20%E2%80%8EAMc%3ATest%20Suite%20for%20Focused%20Build%2Cw%3A%2CT%3D<>'
Second URL :
https://<>//sap/opu/odata/SALM/TM_SERVICE/TILECollection(ConfigID='1%20',PageID='2%20',TileID='8%20')?$format=json&sap-language=EN
Second URL is generic and constant URL but after first execution of first URL with different parameter values second URL will give different response.
In a browser I am able to execute these two URL's and see the response difference with different parameter values.
Need C# Code to simulate the browser behavior. In C# Code second URL response is constant and not being changed.
Tried using following : WebRequest,HttpWebRequest, HttpClient,WebClient along with passing cookie ( cookie received from First response is passed to Request while executing the second url )
var httpClientHandler = new HttpClientHandler()
{
Credentials = credential,
CookieContainer = cookieContainer
};
using (HttpClient client = new HttpClient(httpClientHandler))
{
using (HttpResponseMessage response = client.GetAsync("<<first url>>").Result)
{
Uri uri = new Uri(string.Format(ConfigurationManager.AppSettings["TestPlanSetFilters"], s));
IEnumerable<Cookie> responseCookies = cookieContainer.GetCookies(uri).Cast<Cookie>();
foreach (Cookie cookie in responseCookies)
{
cookieContainer.Add(cookie);
}
}
using (HttpResponseMessage response1 = client.GetAsync("<<second url>>").Result)
{
using (HttpContent content1 = response1.Content)
{
var json2 = content1.ReadAsStringAsync().Result;
}
}
}

How to get Cookie of Request Header after reponse in C#

I want to get cookies "Request Header" not "Reponse Header" of links "https://www.udemy.com/join/login-popup/"
I have used WebRequest and HttpClient but when I did not see Cookie debug in it
Please help me write it in C # I find trying to find every possible way but I still have not found.
Using System.Net.Http.HttpClient for example, we can get cookies like following:
var myClientHandler = new HttpClientHandler();
myClientHandler.CookieContainer = new CookieContainer();
var client = new HttpClient(myClientHandler);
var response = await client.GetAsync("https://www.udemy.com/join/login-popup/");
var cookieCollection = myClientHandler.CookieContainer.GetCookies(new Uri("https://www.udemy.com/join/login-popup/"));
foreach (var cookie in cookieCollection.Cast<Cookie>())
{
Debug.WriteLine(cookie);
}
HttpClient saves cookies that are sent by a server and automatically add them to subsequent requests to that URI within the same app container. So if we didn't modify cookies manually, the cookies in "Request Header" should be the same as what returned in previous reponse.

C# HttpClient 303 SeeOther bug

HttpClient automatically handles 303 (SeeOther) as described in https://msdn.microsoft.com/en-us/library/system.net.httpstatuscode(v=vs.118).aspx
SeeOther automatically redirects the client to the URI specified in
the Location header as the result of a POST. The request to the
resource specified by the Location header will be made with a GET.
The same behavior seems to happen on GET requests, but the call fails as if my Authorization header was missing.
I set the header using
public void SetOauthToken(String key, String token)
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(key, token);
}
I am able to fix the error by intercepting the 303 and calling the URI from Location header by hand.
For example I change
public async Task<T> GetXML<T>(String url)
{
using (HttpResponseMessage response = await client.GetAsync(url))
{
string result = await response.Content.ReadAsStringAsync();
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(new StringReader(result));
}
}
to
public async Task<T> GetXML<T>(String url)
{
using (HttpResponseMessage response = await client.GetAsync(url))
{
if (response.StatusCode == HttpStatusCode.SeeOther)
{
return await GetXML<T>(response.Headers.Location.ToString());
}
else
{
string result = await response.Content.ReadAsStringAsync();
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(new StringReader(result));
}
}
}
and it fixes the bug.
When examining the redirect request everything seems fine. The redirect call works from Postman.
Any idea why is this happening?
First, don't always believe the http status responses. Websites often change the values to make it harder for hackers to get access to the server. When a Http connection is made a negotiation takes place between client and server using the http headers. The negotiation attempts to get a common mode for the transfer. For example a server may be designed to work in English, French, and German. The client includes English in the header so the client will then connect to the English webpages. So you leaving out the AuthenticationHeaderValue is not a bug, but a line of code that is required. A IE webpage is more robust than the Net Http Client and sends more default headers. If you used a sniffer like wireshark or fiddler and compared the headers using an IE and you application you would see the differences.

How can I tell if HttpClient is connecting via HTTPS

I am using HttpClient to talk to an API. The server will automatically redirect http:// requests to https:// if enabled. So, in order to protect the users API key I want to create a test connection to the website to see if I am redirected before sending over the API Key.
The HttpClient redirects properly, but I cannot seem to find a decent way to find out if the client is using HTTPS or not. I know I could test whether https:// exists within response.RequestMessage.RequestUri but this seems a little flaky
public void DetectTransportMethod()
{
using (HttpClient client = new HttpClient())
{
client.Timeout = TimeSpan.FromSeconds(20);
using (HttpResponseMessage response = client.GetAsync(this.HttpHostname).Result)
{
using (HttpContent content = response.Content)
{
// check if the method is HTTPS or not
}
}
}
}
The documentation of HttpClient (MSDN) and HttpResponseMessage (MSDN) do not contain any methods that can be used to determine whether a request has been made over https. Even though checking the URI of the HttpResponseMessage does indeed sound flaky I'm afraid it's the easiest and most readable option. Implementing this as a extension method for HttpResponseMessage probably is the most readable. To ensure that the HttpClient you are using can be redirected make sure that the WebRequestHandler (MSDN) passed into the HttpClient has the AllowAutoRedirect (MSDN) property set to true.
See the following extension method:
static class Extensions
{
public static bool IsSecure(this HttpResponseMessage message)
{
rreturn message.RequestMessage.RequestUri.Scheme == "https";
}
}
And the following Console application that demonstrates it works. For this to work however, the HTTP server has to upgrade the connection to https (Like Facebook does) and the AllowAutoRedirect property of the WebRequestHandler has to be set to true.
static void Main(string[] args)
{
using (var client = new HttpClient(new HttpClientHandler { AllowAutoRedirect = true}))
{
client.Timeout = TimeSpan.FromSeconds(20);
using (var response = client.GetAsync("http://www.geenstijl.nl").Result)
{
Console.WriteLine(response.IsSecure());
}
using (var response = client.GetAsync("http://www.facebook.com").Result)
{
Console.WriteLine(response.IsSecure());
}
}
Console.ReadLine();
}

Categories

Resources