How do I connect to the Apple News API from c#? - c#

I'm trying to connect to Apple's News API. When generating the signature the results all appear the same from each of the different examples. However, I keep getting this error:
{"errors":[{"code":"WRONG_SIGNATURE"}]}
Here is my c# code to generate the auth header.
public class Security
{
public static string AuthHeader(string method, string url, object content=null)
{
var apiKeyId = "<YOUR KEY HERE...>"; //we get this value from our settings file.
var apiKeySecret = "<YOUR SECRET HERE...>"; //we get this value from our settings file.
if ( string.IsNullOrEmpty(apiKeyId) || string.IsNullOrEmpty(apiKeySecret)) return string.Empty;
var encoding = new ASCIIEncoding();
var dt = DateTime.Now.ToString(Constants.DateFormat);
var canonicalRequest = string.Format("{0}{1}{2}", method, url, dt);
var key = Convert.FromBase64String(apiKeySecret);
var hmac = new HMACSHA256(key);
var hashed = hmac.ComputeHash(encoding.GetBytes(canonicalRequest));
var signature = Convert.ToBase64String(hashed);
var authorizaton = string.Format(#"HHMAC; key={0}; signature={1}; date={2}", apiKeyId, signature, dt);
return authorizaton;
}
}
Short version of Constants class
public static class Constants
{
public static readonly string ChannelId = "<YOUR CHANNEL ID HERE...>"; //again from our settings file
public static readonly string BaseUrl = "https://news-api.apple.com";
public static readonly string DateFormat = "yyyy-MM-ddTHH:mm:ssK";
}
Short version of Actions class (SendCommand is the method that performs the request)
public class Actions
{
public static string SendCommand(string action, string method)
{
var url = $"{Constants.BaseUrl}{action}";
var authheader = Security.AuthHeader(method, url, null);
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Timeout = 1000;
request.Headers.Add("Authorization", authheader);
request.Accept = "application/json";
request.ContentType = "application/json";
var output = string.Empty;
try
{
using (var response = request.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
output = reader.ReadToEnd();
}
}
catch (WebException e)
{
using (var reader = new StreamReader(e.Response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
public static string ReadChannel()
{
var action = $"/channels/{Constants.ChannelId}";
const string method = "GET";
return SendCommand(action, method);
}
}
I'm using the ReadChannel method for testing.
I've also tried examples in php and ruby with no luck.
Any ideas how to do this correctly?

Pasted the authorization string generated from the original code on this post into fiddler and I was able to get a successful response from the Apple News API. It seems like HttpWebRequest isn't including the Authorization header correctly and submitting the same request with the property PreAuthenticate = true corrects this issue (HttpWebRequest.PreAuthenticate). Also, with a GET request the ContentType needs to be omitted so I've added a conditional statement to account for this too.
public class Actions
{
public static string SendCommand(string action, string method)
{
var url = $"{Constants.BaseUrl}{action}";
var authheader = Security.AuthHeader(method, url, null);
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Timeout = 1000;
request.PreAuthenticate = true;
request.Headers.Add("Authorization", authheader);
request.Accept = "application/json";
if(method.Equals("post", StringComparison.InvariantCultureIgnoreCase))
request.ContentType = "application/json";
var output = string.Empty;
try
{
using (var response = request.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
output = reader.ReadToEnd();
}
}
catch (WebException e)
{
using (var reader = new StreamReader(e.Response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
public static string ReadChannel()
{
var action = $"/channels/{Constants.ChannelId}";
const string method = "GET";
return SendCommand(action, method);
}
}

The error complains something is wrong in how we compute the signature. Let's take a look at the Apple's example code to produces a correct signature, which is here:
https://developer.apple.com/documentation/apple_news/apple_news_api/about_the_news_security_model
Unfortunately I only found Python code. I don't know Python, either, but I can figure out enough to adapt it to just show the signature. We'll also need to know exactly what date value was used.
import base64
from hashlib import sha256
import hmac
from datetime import datetime
channel_id = 'cdb737aa-FFFF-FFFF-FFFF-FFFFFFFFFFFF'
api_key_id = '240ab880-FFFF-FFFF-FFFF-FFFFFFFFFFFF'
api_key_secret = 'HgyfMPjFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF='
url = 'https://news-api.apple.com/channels/%s' % channel_id
date = str(datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"))
canonical_request = 'GET' + url + date
key = base64.b64decode(api_key_secret)
hashed = hmac.new(key, canonical_request, sha256)
signature = hashed.digest().encode("base64").rstrip('\n')
print date
print signature
You can see the result here (the short link points to tutorialspoint.com, which was the first online python interpreter I found in Google):
http://tpcg.io/e1W4p1
If you don't trust the link, just know I was able to use it to figure out the following known-correct signature based on these known inputs:
Method: GET
URL: https://news-api.apple.com/channels/cdb737aa-FFFF-FFFF-FFFF-FFFFFFFFFFFF
DateTime: 2018-06-12T18:15:45Z
API Secret: HgyfMPjFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=
Signature: f3cOzwH7HGYPg481noBFwKgVOGAhH3jy7LQ75jVignA=
Now we can write C# code we can verity. When we can use those same inputs to produce the same signature result, we will have correct code.
Based on that, I was able to write this C# code:
public static void Main()
{
string method = "GET";
string url = "https://news-api.apple.com/channels/cdb737aa-FFFF-FFFF-FFFF-FFFFFFFFFFFF";
string dateString = "2018-06-12T18:15:45Z";
string apiKeySecret = "HgyfMPjFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=";
var MyResult = GetSignature(method, url, dateString, apiKeySecret);
var DocumentedResult = "f3cOzwH7HGYPg481noBFwKgVOGAhH3jy7LQ75jVignA=";
Console.WriteLine(MyResult);
Console.WriteLine(MyResult == DocumentedResult);
}
public static string GetSignature(string method, string url, string dt, string APISecret)
{
var hmac = new HMACSHA256(Convert.FromBase64String(APISecret));
var hashed = hmac.ComputeHash(Encoding.ASCII.GetBytes(method + url + dt));
return Convert.ToBase64String(hashed);
}
Which you can see in action here:
https://dotnetfiddle.net/PQ73Zv
I don't have my own Apple API key to test with, so that's as far as I can take you.
One thing I did notice from the question is Apple's example has a "Z" at the end of the date string which is missing with original code here.

Related

How to Return the HTTP Call Response in C#

I am trying to return the response of productRating in this HTTP call. I have tried several ways but haven't been successful. Any idea? The response is a json with an object that I want to get access to the streamInfo>avgRatings>_overall
public double GetRatings(string productId)
{
const string URL = "https://comments.au1.gigya.com/comments.getStreamInfo";
var apiKey = "3_rktwTlLYzPlqkzS62-OxNjRDx8jYs-kV40k822YlHfEx5VCu93fpUo8JtaKDm_i-";
var categoryId = "product-ratings";
var urlParams = "?apiKey=" + apiKey + "&categoryID=" + categoryId + "&streamID=" + productId + "";
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(URL);
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var response = JsonConvert.DeserializeObject(client.GetAsync(urlParams).Result.Content.ReadAsStringAsync().Result);
var productrating = (response["streamInfo"]["avgRatings"]["_overall"]);
return;
}
GetRatings Erroe: not all code paths return a value.
Productrating Error:can not apply indexing with [] to an expression of type HttpResponseMessage
return Error: an object of a type convertable to double is required
ApiCall has to return something. Looking at the example below.
#functions {
public static string ApiCall()
{
return "Hello World!";
}
}
#{
var ratings = ApiCall();
}
#if (ratings != null)
{
<div class="gig-rating-stars" content=#ratings></div>
}
I am using this function for POST API Call and it returns response string and if you want to add security features you can send it in JObject because its the secure channel to send Jobject as a parameter on URL
## Code Start ##
protected string RemoteApiCallPOST(JObject elements, string url)
{
try
{
var httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
httpWebRequest.ContentType = "application/json";
httpWebRequest.Method = "POST";
// JObject jsonResult = new JObject();
string id;
using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
{
streamWriter.Write(elements);
streamWriter.Flush();
streamWriter.Close();
}
var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
id = (streamReader.ReadToEnd()).ToString();
return id;
}
}
catch (Exception EX)
{
return "";
}
}
Code End
The only change I needed to do is changing "var" to "dynamic" in :
dynamic response = JsonConvert.DeserializeObject(client.GetAsync(urlParams).Result.Content.ReadAsStringAsync().Result);

C# Make HttpWebResponse wait until response has been received instead of receiving HTTP 400 Error

I am trying to access Adobe's Datawarehouse reporting using their API. The way it works is by first forming the JSON using the metrics available and the dimensions you want it to be brojen down by. That all works fine. However where I do have the issue comes down to this. Whenever I request more than 3 to 4 dimensions, the report takes longer to generate. I would need the HttpWebResponse to wait until it has been completed before moving to the next line. Below is my sample code that I have built based on an example from github.
class Program
{
protected static string JSONFolder = System.Configuration.ConfigurationManager.AppSettings["JSONFolder"];
protected static string USERNAME = System.Configuration.ConfigurationManager.AppSettings["Username"];
protected static string SECRET = System.Configuration.ConfigurationManager.AppSettings["Secret"];
protected static string ReportSuiteID = System.Configuration.ConfigurationManager.AppSettings["ReportSuiteID"];
private static string ENDPOINT = "https://api5.omniture.com/admin/1.4/rest/";
protected static string environment = "dev";
static void Main(string[] args)
{
DateTime previousDay = DateTime.Today.AddDays(-1);
string json = RequestJsonBuilder(previousDay, previousDay, ReportSuiteID);
string response = callMethod("Report.Queue", json, 15);
var jss = new JavaScriptSerializer();
var requestDetails = jss.Deserialize<Dictionary<string, string>>(response);
}
/*Build the json for the methods Report.GetStatus and Report.Get*/
public static string RequestJsonBuilderStatus(string id)
{
ReportID json = new ReportID() { reportID = id };
var serializer = new JavaScriptSerializer();
var serializedResult = serializer.Serialize(json);
return serializedResult;
}
/*Build the json for the method Report.Queue*/
static string RequestJsonBuilder(DateTime StartDate, DateTime EndDate, string ReportSuiteID)
{
//Build the list of metrics to send with the request
var listMetrics = new List<Metrics>();
listMetrics.Add(new Metrics() { id = "visits" });
//Build the list of elements to send with the request
var listElements = new List<Elements>();
listElements.Add(new Elements() { id = "page" , top = "25"});
var serializer2 = new JavaScriptSerializer();
Dictionary<string, RankedRequest> dic = new Dictionary<string, RankedRequest>();
dic.Add("reportDescription", new RankedRequest()
{
reportSuiteID = ReportSuiteID,
dateFrom = StartDate.ToString("yyyy-MM-dd"),
dateTo = EndDate.ToString("yyyy-MM-dd"),
metrics = listMetrics,
elements = listElements,
source = "warehouse"
});
var serializedResult2 = serializer2.Serialize(dic);
return serializedResult2;
}
/*Build the rest call to the Adobe Analytics REST APII 1.4*/
public static String callMethod(String method, String data, int secs)
{
Program prog = new Program();
HttpWebResponse statusResponse = null;
string responseXml = "";
StringBuilder sbUrl = new StringBuilder(ENDPOINT + "?method=" + method);
HttpWebRequest omniRequest = (HttpWebRequest)WebRequest.Create(sbUrl.ToString());
string timecreated = generateTimestamp();
string nonce = generateNonce();
string digest = getBase64Digest(nonce + timecreated + SECRET);
nonce = base64Encode(nonce);
omniRequest.Headers.Add("X-WSSE: UsernameToken Username=\"" + USERNAME + "\", PasswordDigest=\"" + digest + "\", Nonce=\"" + nonce + "\", Created=\"" + timecreated + "\"");
omniRequest.Method = "POST";
omniRequest.ContentType = "text/json";
//Write the json details to the request
using (var streamWriter = new StreamWriter(omniRequest.GetRequestStream()))
{
string json = data;
Console.WriteLine("\n 2.0 ############## Json request : \n\n " + json + "\n\n");
streamWriter.Write(json);
}
//Get the response of the request
try
{
Console.WriteLine("\n 2.0 ############## Sleeping thread for " + secs + "\n");
using (HttpWebResponse statusResponse = (HttpWebResponse) omniRequest.GetResponse())
{
using (Stream receiveStream = statusResponse.GetResponseStream())
{
using (StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8))
{
responseXml = readStream.ReadToEnd();
return responseXml;
}
}
}
}
catch (Exception ex) { throw ex; }
}
// other methods defined below
// private static string getBase64Digest(string input)
// private static string generateNonce()
// public static string base64Encode(string data)
// etc.
}
How do I make HttpWebResponse wait until a HTTP 200 response is actually received. This might take minutes to sometimes an hour depending on the number of metrics and dimensions I add.
Things I have tried also include changing line 88 to something like this:
How to process WebResponse when .NET throws WebException ((400) Bad Request)?
how to wait until a web request with HttpWebRequest is finished?
Sincerely appreciate all help here.
Thanks
Did you try setting the timeout? I was looking recently for the similar question and found that one:
HttpWebRequest.GetResponse() keeps getting timed out

Google reCaptcha in Web API 2 c#

I have an ASP.NET Web API 2 Project. I am trying to read Google Captcha from the form.
I tried this Code:
public string Post(FoundingRequest model)
{
var response = Request["g-recaptcha-response"];
string secretKey = "my key";
var client = new WebClient();
var result = client.DownloadString(
$"https://www.google.com/recaptcha/api/siteverify?secret={secretKey}&response={response}");
var obj = JObject.Parse(result);
model.Captcha = (bool)obj.SelectToken("success");
....
}
but I am receiving an Error on the first line:
Cannot apply indexing with [] to an expression of type
'HttpRequestMessage'
why? and how to solve it? thank you
That method's body for me works fine:
const string secretKey = "YOUR KEY";
string responseFromServer = "";
HttpWebRequest req = (HttpWebRequest)WebRequest.Create("https://www.google.com/recaptcha/api/siteverify?secret=" + secretKey + "&response=" + response);
using (WebResponse resp = req.GetResponse())
using (Stream dataStream = resp.GetResponseStream())
{
if (dataStream != null)
{
using (StreamReader reader = new StreamReader(dataStream))
{
// Read the content.
responseFromServer = reader.ReadToEnd();
}
}
}
dynamic jsonResponse = new JavaScriptSerializer().DeserializeObject(responseFromServer);
return jsonResponse == null || bool.Parse(jsonResponse["success"].ToString());
Update
Regarding the comment, it can be checked on the client side
var response = window.grecaptcha.getResponse()
And then pass this variable to Web API
This is part of my client script:
if (typeof window.grecaptcha != 'undefined') {
var capResponse = window.grecaptcha.getResponse();
if (!capResponse || capResponse.length === 0) {
user.isCaptchaPassed = false;
//alert("Captcha not Passed");
return false;
}
user.gReCaptcha = capResponse;
}
"user" is JS object created before, which passed through JS to server. (AJAX call)
Here's how I did it. I don't use the ChallangeTs so I didn't bother
trying to figure out why it wasn't converting to DateTime properly.
Maybe someone else has that solved.
public class ReCaptchaResponse
{
public bool Success;
public string ChallengeTs;
public string Hostname;
public object[] ErrorCodes;
}
[HttpPost]
[Route("captcha")]
public bool Captcha([FromBody] string token)
{
bool isHuman = true;
try
{
string secretKey = ConfigurationManager.AppSettings["reCaptchaPrivateKey"];
Uri uri = new Uri("https://www.google.com/recaptcha/api/siteverify" +
$"?secret={secretKey}&response={token}");
HttpWebRequest request = WebRequest.CreateHttp(uri);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = 0;
HttpWebResponse response = (HttpWebResponse) request.GetResponse();
Stream responseStream = response.GetResponseStream();
StreamReader streamReader = new StreamReader(responseStream);
string result = streamReader.ReadToEnd();
ReCaptchaResponse reCaptchaResponse = JsonConvert.DeserializeObject<ReCaptchaResponse>(result);
isHuman = reCaptchaResponse.Success;
}
catch (Exception ex)
{
Trace.WriteLine("reCaptcha error: " + ex);
}
return isHuman;
}
I found the answer, I created a hidden input with a certain name and updated its value on Captcha call back. Code:
<input type="hidden" value="" id="recaptcha" name="recaptcha" />
<div class="g-recaptcha" data-callback="imNotARobot" data-sitekey="key"></div>
and the Javascript is:
<script type="text/javascript">
var imNotARobot = function () {
$("#recaptcha").val(grecaptcha.getResponse());
};
</script>
server side:
public string Recaptcha { get; set; }
and the model binder does all the work.
I assume this request is coming from a form, change this:
var response = Request["g-recaptcha-response"];
To this:
var response = Request.Form["g-Recaptcha-Response"];
Also Change this:
var result = client.DownloadString($"https://www.google.com/recaptcha/api/siteverify?secret={secretKey}&response={response}");
To this:
var result = client.DownloadString(string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", secretKey, response));

Get retweets of a tweet in C# oAuthTwitterWrapper

My goal is to get all the retweet IDs of a specific tweet. I tried to use oAuthTwitterWrapper for C# twitter authentication and tried changing the code, but without any success. I am getting Forbidden message from twitter when I change the searchFormat to suit my requirement.
Someone please help!
oAuthTwitterWrapper Wrapper - https://github.com/andyhutch77/oAuthTwitterWrapper
Stack Exchange Link - Authenticate and request a user's timeline with Twitter API 1.1 oAuth
Thanks in advance..
Ok, I think this is how you would do this manually to begin with.
I have not tested it so there may be some typos, let me know and I will update it the answer accordingly.
This makes a call to the api specified here:
https://dev.twitter.com/docs/api/1.1/get/statuses/retweets/%3Aid
// You need to set your own keys and tweet id
var oAuthConsumerKey = "superSecretKey";
var oAuthConsumerSecret = "superSecretSecret";
var oAuthUrl = "https://api.twitter.com/oauth2/token";
var tweetId = "21947795900469248";
// Do the Authenticate
var authHeaderFormat = "Basic {0}";
var authHeader = string.Format(authHeaderFormat,
Convert.ToBase64String(Encoding.UTF8.GetBytes(Uri.EscapeDataString(oAuthConsumerKey) + ":" +
Uri.EscapeDataString((oAuthConsumerSecret)))
));
var postBody = "grant_type=client_credentials";
HttpWebRequest authRequest = (HttpWebRequest)WebRequest.Create(oAuthUrl);
authRequest.Headers.Add("Authorization", authHeader);
authRequest.Method = "POST";
authRequest.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";
authRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
using (Stream stream = authRequest.GetRequestStream())
{
byte[] content = ASCIIEncoding.ASCII.GetBytes(postBody);
stream.Write(content, 0, content.Length);
}
authRequest.Headers.Add("Accept-Encoding", "gzip");
WebResponse authResponse = authRequest.GetResponse();
// deserialize into an object
TwitAuthenticateResponse twitAuthResponse;
using (authResponse)
{
using (var reader = new StreamReader(authResponse.GetResponseStream())) {
JavaScriptSerializer js = new JavaScriptSerializer();
var objectText = reader.ReadToEnd();
twitAuthResponse = JsonConvert.DeserializeObject<TwitAuthenticateResponse>(objectText);
}
}
// Get the retweets by Id
var retweetFormat = "https://api.twitter.com/1.1/statuses/retweets/{0}.json";
var retweetsUrl = string.Format(retweetFormat, tweetId);
HttpWebRequest retweetRequest = (HttpWebRequest)WebRequest.Create(retweetsUrl);
var retweetHeaderFormat = "{0} {1}";
timeLineRequest.Headers.Add("Authorization", string.Format(retweetHeaderFormat, twitAuthResponse.token_type,
twitAuthResponse.access_token));
retweetRequest.Method = "Get";
WebResponse retweetResponse = timeLineRequest.GetResponse();
var retweetJson = string.Empty;
using (retweetResponse)
{
using (var reader = new StreamReader(retweetResponse.GetResponseStream()))
{
retweetJson = reader.ReadToEnd();
}
}
//parse the json from retweetJson to read the returned id's
public class TwitAuthenticateResponse {
public string token_type { get; set; }
public string access_token { get; set; }
}
If this works and you have time please submit a pull request via GitHub and I will include it in oauthtwitterwrapper.

How can I do digest authentication with HttpWebRequest?

Various articles (1, 2) I discovered make this look easy enough:
WebRequest request = HttpWebRequest.Create(url);
var credentialCache = new CredentialCache();
credentialCache.Add(
new Uri(url), // request url
"Digest", // authentication type
new NetworkCredential("user", "password") // credentials
);
request.Credentials = credentialCache;
However, this only works for URLs without URL parameters. For example, I can download http://example.com/test/xyz.html just fine, but when I attempt to download http://example.com/test?page=xyz, the result is a 400 Bad Request message with the following in the server's logs (running Apache 2.2):
Digest: uri mismatch - </test> does not match request-uri </test?page=xyz>
My first idea was that the digest specification requires URL parameters to be removed from the digest hash -- but removing the parameter from the URL passed to credentialCache.Add() didn't change a thing. So it must be the other way around and somewhere in the .NET framework is wrongly removing the parameter from the URL.
You said you removed the querystring paramters, but did you try going all the way back to just the host? Every single example of CredentialsCache.Add() I've seen seems to use only the host, and the docs for CredentialsCache.Add() list the Uri parameter as "uriPrefix", which seems telling.
In other words, try this out:
Uri uri = new Uri(url);
WebRequest request = WebRequest.Create(uri);
var credentialCache = new CredentialCache();
credentialCache.Add(
new Uri(uri.GetLeftPart(UriPartial.Authority)), // request url's host
"Digest", // authentication type
new NetworkCredential("user", "password") // credentials
);
request.Credentials = credentialCache;
If this works, you will also have to make sure that you don't add the same "authority" to the cache more than once... all requests to the same host should be able to make use of the same credential cache entry.
Code taken from this post has worked perfectly for me Implement Digest authentication via HttpWebRequest in C#
I had following issue, when ever I browser the feed url in a browser it asked for username and password and worked fine, however any of the above code samples were not working, on inspecting Request/Response Header (in web developer tools in firefox) i could see header having Authorization of type digest.
Step 1 Add:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Net;
using System.IO;
namespace NUI
{
public class DigestAuthFixer
{
private static string _host;
private static string _user;
private static string _password;
private static string _realm;
private static string _nonce;
private static string _qop;
private static string _cnonce;
private static DateTime _cnonceDate;
private static int _nc;
public DigestAuthFixer(string host, string user, string password)
{
// TODO: Complete member initialization
_host = host;
_user = user;
_password = password;
}
private string CalculateMd5Hash(
string input)
{
var inputBytes = Encoding.ASCII.GetBytes(input);
var hash = MD5.Create().ComputeHash(inputBytes);
var sb = new StringBuilder();
foreach (var b in hash)
sb.Append(b.ToString("x2"));
return sb.ToString();
}
private string GrabHeaderVar(
string varName,
string header)
{
var regHeader = new Regex(string.Format(#"{0}=""([^""]*)""", varName));
var matchHeader = regHeader.Match(header);
if (matchHeader.Success)
return matchHeader.Groups[1].Value;
throw new ApplicationException(string.Format("Header {0} not found", varName));
}
private string GetDigestHeader(
string dir)
{
_nc = _nc + 1;
var ha1 = CalculateMd5Hash(string.Format("{0}:{1}:{2}", _user, _realm, _password));
var ha2 = CalculateMd5Hash(string.Format("{0}:{1}", "GET", dir));
var digestResponse =
CalculateMd5Hash(string.Format("{0}:{1}:{2:00000000}:{3}:{4}:{5}", ha1, _nonce, _nc, _cnonce, _qop, ha2));
return string.Format("Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", " +
"algorithm=MD5, response=\"{4}\", qop={5}, nc={6:00000000}, cnonce=\"{7}\"",
_user, _realm, _nonce, dir, digestResponse, _qop, _nc, _cnonce);
}
public string GrabResponse(
string dir)
{
var url = _host + dir;
var uri = new Uri(url);
var request = (HttpWebRequest)WebRequest.Create(uri);
// If we've got a recent Auth header, re-use it!
if (!string.IsNullOrEmpty(_cnonce) &&
DateTime.Now.Subtract(_cnonceDate).TotalHours < 1.0)
{
request.Headers.Add("Authorization", GetDigestHeader(dir));
}
HttpWebResponse response;
try
{
response = (HttpWebResponse)request.GetResponse();
}
catch (WebException ex)
{
// Try to fix a 401 exception by adding a Authorization header
if (ex.Response == null || ((HttpWebResponse)ex.Response).StatusCode != HttpStatusCode.Unauthorized)
throw;
var wwwAuthenticateHeader = ex.Response.Headers["WWW-Authenticate"];
_realm = GrabHeaderVar("realm", wwwAuthenticateHeader);
_nonce = GrabHeaderVar("nonce", wwwAuthenticateHeader);
_qop = GrabHeaderVar("qop", wwwAuthenticateHeader);
_nc = 0;
_cnonce = new Random().Next(123400, 9999999).ToString();
_cnonceDate = DateTime.Now;
var request2 = (HttpWebRequest)WebRequest.Create(uri);
request2.Headers.Add("Authorization", GetDigestHeader(dir));
response = (HttpWebResponse)request2.GetResponse();
}
var reader = new StreamReader(response.GetResponseStream());
return reader.ReadToEnd();
}
}
}
Step 2: Call new method
DigestAuthFixer digest = new DigestAuthFixer(domain, username, password);
string strReturn = digest.GrabResponse(dir);
if Url is: http://xyz.rss.com/folder/rss
then
domain: http://xyz.rss.com (domain part)
dir: /folder/rss (rest of the url)
you could also return it as stream and use XmlDocument Load() method.
The solution is to activate this parameter in apache:
BrowserMatch "MSIE" AuthDigestEnableQueryStringHack=On
More info : http://httpd.apache.org/docs/2.0/mod/mod_auth_digest.html#msie
Then add this property in your code for the webrequest object:
request.UserAgent = "MSIE"
it work very well for me
I think the second URL points to dynamic page and you should first call it using GET to get the HTML and then to download it. No experience in this field though.
In earlier answers everybody use the obsolete WEbREquest.Create method.
So here is my async solution what up to date for recently trending's:
public async Task<string> LoadHttpPageWithDigestAuthentication(string url, string username, string password)
{
Uri myUri = new Uri(url);
NetworkCredential myNetworkCredential = new NetworkCredential(username, password);
CredentialCache myCredentialCache = new CredentialCache { { myUri, "Digest", myNetworkCredential } };
var request = new HttpClient(new HttpClientHandler() { Credentials = myCredentialCache, PreAuthenticate = true});
var response = await request.GetAsync(url);
var responseStream = await response.Content.ReadAsStreamAsync();
StreamReader responseStreamReader = new StreamReader(responseStream, Encoding.Default);
string answer = await responseStreamReader.ReadToEndAsync();
return answer;
}

Categories

Resources