Httpclinet getasync resetting cookies - c#

I'm working on a cross platform app targeting windows phone8, windows 8, android and Ios. The app needs to get some data from the internet and reuse the data accordingly. So I have a project with few classes inside. One class to Sign in to the website and get the required cookies for further use. Below is the code from this class:-
public static CookieContainer Cookiejar { get; private set; }
public static async Task<CookieContainer> GetCookies()
{
var uri = new Uri("https://mywebsite.com");
Cookiejar = new CookieContainer();
var handler = new HttpClientHandler
{
CookieContainer = Cookiejar,
UseCookies = true,
UseDefaultCredentials = false
};
var client = new HttpClient(handler);
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
HttpContent content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string,string>("username","user"),
new KeyValuePair<string,string>("password","user1234"),
new KeyValuePair<string,string>("customer","AAAA"),
new KeyValuePair<string,string>("doLogin","Logga+in"),
new KeyValuePair<string,string>("language","se")
});
await client.PostAsync(uri, content);
return Cookiejar;
}
Another class is used to get the actual data. I'm passing the CookieContainer from the first class which has CookieHandler as name to the other class which has DataHandler as name. The problem I'm facing is when I use this code in windows phone 8 project and whenever I call httpclient getasync it acts as if I didn't pass the cookies and reset it to xxxxx and the response will be only the log-in page instead of my desired page. Below is the code for DataHandler class:-
public string Response { get; private set; }
public async Task<string> GetResponse(Uri uri)
{
HttpClientHandler handler = new HttpClientHandler();
handler.UseCookies = true;
handler.CookieContainer = CookieHandler.Cookiejar;
HttpClient client = new HttpClient(handler);
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/xhtml+xml"));
var response = await client.GetAsync(uri);
string webresponse = null;
if (response.IsSuccessStatusCode)
{
var resp = response.Content.ReadAsByteArrayAsync().Result;
Encoding encode = Encoding.GetEncoding("iso-8859-1");
var respString = encode.GetString(resp, 0, resp.Length - 1);
webresponse = respString;
}
handler.Dispose();
client.Dispose();
response.Dispose();
return Response = webresponse;
}
Been looking into this problem for few hours now and tested every possible tips I found here and on the net, but nothings work. OBS!!! the cookies are not HtmlOnly and not secure. What really gets me confused is the fact that if I use this exact same code in a windows 8 project it works perfectly but not in windows phone 8 project. Worth noting i'm using the latest BCL system.net.http lib.
Any help and suggestions are more than welcome.

Just wanted to answer this question which might be bothering other developers around the World.
First i would like to thank TheESJ for heading me to the right direction.
Now lets start with what i changed/added to my code to workaround this problem.
I just added the following code lines to my initial POST method:-
var wwwUri = new Uri("https://www.mywebsite.com");
Cookiejar.SetCookies(baseUri, Cookiejar.GetCookieHeader(wwwUri));
It appears that in Windows Phone there is '.' in the cookie header (description below), the above code will make a copy of your cookies with the correct header.
Before I applied the above code lines and the CookieContainer in debugger and look at the m_domainTable it had one item with a value {[.mywebsite.com, System.Net.PathList]} eventhough the domain property value is ‘mywebsite.com’ without ‘.’, while in Windows store app it hade one item with a value {[mywebsite.com, System.Net.PathList]} and the domain property value is ‘mywebsite.com’.
After I applied the above code and it worked I debugged and check CookieContainer, the m_domainTable have now two items, one with this value {[.mywebsite.com, System.Net.PathList]} with the domain property value is ‘mywebsite.com’ and the other with this value {[mywebsite.com, System.Net.PathList]} and the same domain property value. That is why it’s sending the right cookie now as it is sending the 2nd cookie {[mywebsite.com, System.Net.PathList]}.

Related

How can I execute an url call from C# code on a ASP website on a ASCX page?

I need to call a Custom protocol (something like: "custom:signDocs?param1=value?param2=value") that is registered on a client.
I have a working one that is executed via JavaScript on a button click.
But I need to call the url to execute the program I have on the clients pc.
The program is for signing documents and sending them back to the server, and, in the code I have a 15min timer that waits for the status of the documents to change to signed then it shows the documents to the user.
I also tried using webrequest:
//Method that uses the webrequest
{
System.Net.WebRequest.RegisterPrefix("customProtocolName", new PrototipoIDPTRequestCreator());
System.Net.WebRequest req = System.Net.WebRequest.Create(protocolUrlWithParams);
var aux = req.GetResponse();
}
internal class CustomRequestCreator : System.Net.IWebRequestCreate
{
public WebRequest Create(Uri uri)
{
return new CustomWebRequest(uri);
}
}
class CustomWebRequest: WebRequest
{
public override Uri RequestUri { get; }
public CustomWebRequest(Uri uri)
{
RequestUri = uri;
}
}
But this does nothing, I do not know it its even the right path...
Does anyone know of a way to accomplish this?
You can use HttpClient from System.Net.Http like the following example.
Simple get call from a test api endpoint.
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("YOUR_BASE_URL"); //https://localhost:8000
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync("api/test"); //api uri
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
}
Note: For more details refer, HttpClient Doc

Xamarin.Android: How Can I Pass WebView Login Creds Into HttpWebRequest?

I am trying to pass login creds from a WebView into an HttpWebRequest but not having any luck getting an authenticated response. I am able to successfully make the request, but the response is acting like I haven't logged in. My app has 5 WebViews contained within Fragment s and I'm logged in on all of them. I've tried using the CookieSyncManager but it's deprecated and .Sync() didn't work. I've tried a lot of different ways of passing the cookies into the HttpRequest with no success and many hours spent.
One would think this is a simple request; user has logged in within the app; they should be authenticated for all requests. Here's the closest that I've gotten, but the response string is still not the same as through my authenticated WebView :
This attempt parses each Cookie into a string and adds it
public string _cookieString { get; set; }
private class ExtWebViewClient : WebViewClient
{
TheFragment5 _fm5 = new TheFragment5();
public override void OnPageFinished(WebView view, string url)
{
var cookieHeader = Android.Webkit.CookieManager.Instance.GetCookie(url);
var cookiePairs = cookieHeader.Split('&');
_fm5._cookieString = "";
foreach (var cookiePair in cookiePairs)
{
var cookiePieces = cookiePair.Split('=');
if (cookiePieces[0].Contains(":"))
cookiePieces[0] = cookiePieces[0].Substring(0, cookiePieces[0].IndexOf(":"));
cookies.Add(new Cookie
{
Name = cookiePieces[0],
Value = cookiePieces[1]
});
}
foreach (Cookie c in cookies)
{
if (_fm5._cookieString == "")
{
_fm5._cookieString = c.ToString();
}
else
{
_fm5._cookieString += c.ToString();
}
}
}
}
I've also tried just doing:
_fm5._cookieString = cookieHeader.ToString();
but neither of those attempts is working when I add the cookie string into my HttpRequest :
public async void GetNotificationText(string url)
{
//var _cmhc = _cookieMan.HasCookies;
await Task.Run(() =>
{
_notificationHttpRequestInProgress = true;
try
{
var _ctxxx = Android.App.Application.Context;
//URL _url2 = new URL("https://bitchute.com/notifications/");
//HttpURLConnection conn = (HttpURLConnection)_url2.OpenConnection();
//conn.ReadTimeout = 10000 /* milliseconds */;
//conn.ConnectTimeout = 15000 /* milliseconds */;
////conn.SetRequestProperty("Cookie", cookies);
//conn.Connect();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
Uri uri = new Uri(url);
var _req = request;
var _uriii = uri;
var _cookiesss = _fm5._cookieString;
_cookieCon.SetCookies(uri, _cookiesss);
request.CookieContainer = _cookieCon;
//request.CookieContainer.SetCookies(uri, _cookiesss);
request.AutomaticDecompression = DecompressionMethods.GZip;
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (Stream stream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(stream))
{
_notificationRawText = reader.ReadToEnd();
Console.WriteLine(_notificationRawText);
_rawNoteText = _notificationRawText;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
_notificationHttpRequestInProgress = false;
});
}
This returns, but not the authenticated webtext request; I get the same response any user would get on a browser having never logged in. If I were to browse out to this same url on any WebView in my app, I'd get a completely different response.
You will also notice some commented out code that was another failed attempt at adding the cookies into a connection. I had also tried using HttpURLConnection.SetRequestProperty("Cookie", cookies);
where cookies was a CookieCollection and that didn't work either. The code is mostly commented out and layered because I've been trying this for days.
Does anyone know how I can pass WebView cookies into an HttpRequest using Xamarin.Android?
I am putting this code below in Fragment5 of my app; you can see and compile the full context here:
https://github.com/hexag0d/BitChute_Mobile_Android_BottomNav/blob/NotificationAdder/Fragments/TheFragment5.cs
I'm not sure exactly why the above example didn't work; maybe if you're better at .NET than I am, you could figure it out. However, I was able to successfully pass WebView creds into an HttpClient by following these steps, which are returning an authenticated response. This may not be the most elegant way of doing it, but you can always refine my answer, or post a better one.
What I had to do was set the HttpClient.DefaultRequestHeaders using the .Add() method like this: _client.DefaultRequestHeaders.Add("Cookie", TheFragment5._cookieHeader);
I got the CookieHeader (which is just a string btw) like this:
//instantiate a string that will house our cookie header
public static string _cookieHeader;
//you might want to make it private to prevent abuse
//but this example is just for demonstration
//the thing is we need a string to house our headers in scope of both the WebView and the HttpClient
//extend the WebViewClient
private class ExtWebViewClient : WebViewClient
{
public override void OnPageFinished(WebView view, string url)
{
//I get the cookies when the page finishes loading because
//then we know the cookie has our login cred header
//also, most of the previous examples got the cookies OnPageFinished
TheFragment5._cookieHeader = Android.Webkit.CookieManager.Instance.GetCookie(url);
}
}
Then we need another method for the HttpClient and HttpClientHandler ... mine scans a webpage for notification text.
public async void GetNotificationText(string url)
{
await Task.Run(() =>
{
/* this line is pretty important,
we need to instantiate an HttpClientHandler
then set it's UseCookies property to false
so that it doesn't override our cookies
*/
HttpClientHandler handler = new HttpClientHandler() { UseCookies = false };
try
{
Uri _notificationURI = new Uri("https://bitchute.com/notifications/");
//instantiate HttpClient using the handler
using (HttpClient _client = new HttpClient(handler))
{
//this line is where the magic happens;
//we set the DefaultRequestHeaders with the cookieheader we got from WebViewClient.OnPageFinished
_client.DefaultRequestHeaders.Add("Cookie", TheFragment5._cookieHeader);
//do a GetAsync request with our cookied up client
var getRequest = _client.GetAsync("https://bitchute.com/notifications/").Result;
//resultContent is the authenticated html string response from the server, ready to be parsed =]
var resultContent = getRequest.Content.ReadAsStringAsync().Result;
/*
I was writing to console to check the
response.. for me, I am now getting
the authenticated notification html
page
*/
Console.WriteLine(resultContent);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
Hope this helps you, posting for future reference, especially for people using Xamarin.Android.

HttpClient is modifying my original request to change domains

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.

How do I get the destination URL of a shortened URL?

I have an API (https://www.readability.com/developers/api/parser#idm386426118064) to extract the contents of the webapges, but on passing a shortened url or an url that redirects to other, it gives error.
I am developing windows phone 8.1 (xaml) app. Is there any way to get the destination url in c# or any work around?
eg url - http://www.bing.com/r/2/BB7Q4J4?a=1&m=EN-IN
You could intercept the Location header value before the HttpClient follows it like this:
using (var handler = new HttpClientHandler())
{
handler.AllowAutoRedirect = false;
using (var client = new HttpClient(handler))
{
var response = await client.GetAsync("shortUrl");
var longUrl = response.Headers.Location.ToString();
}
}
This solution will always be the most efficient because it only issue one request.
It is possible however, that the short url will reference another short url and consequently cause this method to fail.
An alternative solution would be to allow the HttpClient to follow the Location header value and observe the destination:
using (var client = new HttpClient())
{
var response = client.GetAsync("shortUrl").Result;
var longUrl = response.RequestMessage.RequestUri;
}
This method is both terser and more reliable than the first.
The drawback is that this code will issue two requests instead of one.
You can get the ResponseUri from GetResponse():
string redirectedURL = WebRequest.Create("http://www.bing.com/r/2/BB7Q4J4?a=1&m=EN-IN")
.GetResponse()
.ResponseUri
.ToString();
Interesting article, by the way.
You need to inspect the headers returned from the URL.
If you get HTTP return codes 301 or 302, then you are being notified that the page is redirecting you to another URL.
See http://www.w3.org/Protocols/HTTP/HTRESP.html for more details about HTTP return codes.

Problems authenticating to website from code

I am trying to write code that will authenticate to the website wallbase.cc. I've looked at what it does using Firfebug/Chrome Developer tools and it seems fairly easy:
Post "usrname=$USER&pass=$PASS&nopass_email=Type+in+your+e-mail+and+press+enter&nopass=0" to the webpage "http://wallbase.cc/user/login", store the returned cookies and use them on all future requests.
Here is my code:
private CookieContainer _cookies = new CookieContainer();
//......
HttpPost("http://wallbase.cc/user/login", string.Format("usrname={0}&pass={1}&nopass_email=Type+in+your+e-mail+and+press+enter&nopass=0", Username, assword));
//......
private string HttpPost(string url, string parameters)
{
try
{
System.Net.WebRequest req = System.Net.WebRequest.Create(url);
//Add these, as we're doing a POST
req.ContentType = "application/x-www-form-urlencoded";
req.Method = "POST";
((HttpWebRequest)req).Referer = "http://wallbase.cc/home/";
((HttpWebRequest)req).CookieContainer = _cookies;
//We need to count how many bytes we're sending. Post'ed Faked Forms should be name=value&
byte[] bytes = System.Text.Encoding.ASCII.GetBytes(parameters);
req.ContentLength = bytes.Length;
System.IO.Stream os = req.GetRequestStream();
os.Write(bytes, 0, bytes.Length); //Push it out there
os.Close();
//get response
using (System.Net.WebResponse resp = req.GetResponse())
{
if (resp == null) return null;
using (Stream st = resp.GetResponseStream())
{
System.IO.StreamReader sr = new System.IO.StreamReader(st);
return sr.ReadToEnd().Trim();
}
}
}
catch (Exception)
{
return null;
}
}
After calling HttpPost with my login parameters I would expect all future calls using this same method to be authenticated (assuming a valid username/password). I do get a session cookie in my cookie collection but for some reason I'm not authenticated. I get a session cookie in my cookie collection regardless of which page I visit so I tried loading the home page first to get the initial session cookie and then logging in but there was no change.
To my knowledge this Python version works: https://github.com/sevensins/Wallbase-Downloader/blob/master/wallbase.sh (line 336)
Any ideas on how to get authentication working?
Update #1
When using a correct user/password pair the response automatically redirects to the referrer but when an incorrect user/pass pair is received it does not redirect and returns a bad user/pass pair. Based on this it seems as though authentication is happening, but maybe not all the key pieces of information are being saved??
Update #2
I am using .NET 3.5. When I tried the above code in .NET 4, with the added line of System.Net.ServicePointManager.Expect100Continue = false (which was in my code, just not shown here) it works, no changes necessary. The problem seems to stem directly from some pre-.Net 4 issue.
This is based on code from one of my projects, as well as code found from various answers here on stackoverflow.
First we need to set up a Cookie aware WebClient that is going to use HTML 1.0.
public class CookieAwareWebClient : WebClient
{
private CookieContainer cookie = new CookieContainer();
protected override WebRequest GetWebRequest(Uri address)
{
HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
request.ProtocolVersion = HttpVersion.Version10;
if (request is HttpWebRequest)
{
(request as HttpWebRequest).CookieContainer = cookie;
}
return request;
}
}
Next we set up the code that handles the Authentication and then finally loads the response.
var client = new CookieAwareWebClient();
client.UseDefaultCredentials = true;
client.BaseAddress = #"http://wallbase.cc";
var loginData = new NameValueCollection();
loginData.Add("usrname", "test");
loginData.Add("pass", "123");
loginData.Add("nopass_email", "Type in your e-mail and press enter");
loginData.Add("nopass", "0");
var result = client.UploadValues(#"http://wallbase.cc/user/login", "POST", loginData);
string response = System.Text.Encoding.UTF8.GetString(result);
We can try this out using the HTML Visualizer inbuilt into Visual Studio while staying in debug mode and use that to confirm that we were able to authenticate and load the Home page while staying authenticated.
The key here is to set up a CookieContainer and use HTTP 1.0, instead of 1.1. I am not entirely sure why forcing it to use 1.0 allows you to authenticate and load the page successfully, but part of the solution is based on this answer.
https://stackoverflow.com/a/10916014/408182
I used Fiddler to make sure that the response sent by the C# Client was the same as with my web browser Chrome. It also allows me to confirm if the C# client is being redirect correctly. In this case we can see that with HTML 1.0 we are getting the HTTP/1.0 302 Found and then redirects us to the home page as intended. If we switch back to HTML 1.1 we will get an HTTP/1.1 417 Expectation Failed message instead.
There is some information on this error message available in this stackoverflow thread.
HTTP POST Returns Error: 417 "Expectation Failed."
Edit: Hack/Fix for .NET 3.5
I have spent a lot of time trying to figure out the difference between 3.5 and 4.0, but I seriously have no clue. It looks like 3.5 is creating a new cookie after the authentication and the only way I found around this was to authenticate the user twice.
I also had to make some changes on the WebClient based on information from this post.
http://dot-net-expertise.blogspot.fr/2009/10/cookiecontainer-domain-handling-bug-fix.html
public class CookieAwareWebClient : WebClient
{
public CookieContainer cookies = new CookieContainer();
protected override WebRequest GetWebRequest(Uri address)
{
var request = base.GetWebRequest(address);
var httpRequest = request as HttpWebRequest;
if (httpRequest != null)
{
httpRequest.ProtocolVersion = HttpVersion.Version10;
httpRequest.CookieContainer = cookies;
var table = (Hashtable)cookies.GetType().InvokeMember("m_domainTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance, null, cookies, new object[] { });
var keys = new ArrayList(table.Keys);
foreach (var key in keys)
{
var newKey = (key as string).Substring(1);
table[newKey] = table[key];
}
}
return request;
}
}
var client = new CookieAwareWebClient();
var loginData = new NameValueCollection();
loginData.Add("usrname", "test");
loginData.Add("pass", "123");
loginData.Add("nopass_email", "Type in your e-mail and press enter");
loginData.Add("nopass", "0");
// Hack: Authenticate the user twice!
client.UploadValues(#"http://wallbase.cc/user/login", "POST", loginData);
var result = client.UploadValues(#"http://wallbase.cc/user/login", "POST", loginData);
string response = System.Text.Encoding.UTF8.GetString(result);
You may need to add the following:
//get response
using (System.Net.WebResponse resp = req.GetResponse())
{
foreach (Cookie c in resp.Cookies)
_cookies.Add(c);
// Do other stuff with response....
}
Another thing that you might have to do is, if the server responds with a 302 (redirect) the .Net web request will automatically follow it and in the process you might lose the cookie you're after. You can turn off this behavior with the following code:
req.AllowAutoRedirect = false;
The Python you reference uses a different referrer (http://wallbase.cc/start/). It is also followed by another post to (http://wallbase.cc/user/adult_confirm/1). Try the other referrer and followup with this POST.
I think you are authenticating correctly, but that the site needs more info/assertions from you before proceeding.

Categories

Resources