CFHTTPAuthenticationCreateFromResponse fails to parse response - ios

I'm writing iOS code to authenticate with an HTTPS proxy server, and need to use the Core Foundation APIs in order to construct the "Proxy-Authorization" header. I'm blocked trying to build a valid CFHTTPAuthenticationRef object, though.
Upon receiving the '407 Proxy authentication required' response with a 'Proxy-Authenticate: Basic realm="example.com"' header, I'm calling CFHTTPAuthenticationCreateFromResponse. This returns a CFHTTPAuthenticationRef object that is invalid; the error is -1000, or kCFStreamErrorHTTPAuthenticationTypeUnsupported.
From looking at the very outdated source code available at http://www.opensource.apple.com/source/CFNetwork/CFNetwork-129.20/HTTP/CFHTTPAuthentication.c, this should all be working fine. I've verified in the debugger that some of the internal structures, like _schemes, are in fact being parsed properly, so I know my header value is correct. Clearly the code inside CFHTTPAuthenticationCreateFromResponse has changed, but I'm at a loss as to what I need to do to make this work.
From stepping through the assembly code, it seems like it may be related to a lack of URL. There is no public API to add a URL to a response, and the fact it's not needed for Basic and Digest auth is odd.
I've observed this on OSX Mavericks, iOS 7 simulator, and iOS 8 simulator. I've boiled this down to a simple repro function, pasted below. I've also tried using a test proxy server and CFReadStream, creating the response from CFReadStreamCopyProperty(requestStream, kCFStreamPropertyHTTPResponseHeader), to no avail (which, presumably, would associate a URL with the response object).
void testCFAuthentication()
{
Boolean result;
CFStringRef str;
const char *rawResponse = "HTTP/1.1 407 Proxy authentication required\r\nProxy-Authenticate: Basic realm=\"example.com\"\r\n\r\n";
CFHTTPMessageRef responseMessage = NULL;
responseMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false);
// Optional hack, doesn't help
/*
CFURLRef url = NULL;
url = CFURLCreateWithString(NULL, CFSTR("https://example.com/"), NULL);
_CFHTTPMessageSetResponseURL(responseMessage, url);
NSLog(#"_CFHTTPMessageSetResponseURL called\n");
*/
result = CFHTTPMessageAppendBytes(responseMessage, (uint8_t *)rawResponse, strlen(rawResponse));
NSLog(#"CFHTTPMessageAppendBytes result: %d\n", (int)result);
NSLog(#"CFHTTPMessageIsHeaderComplete: %d\n", (int)CFHTTPMessageIsHeaderComplete(responseMessage));
NSLog(#"CFHTTPMessageGetResponseStatusCode: %d\n", (int)CFHTTPMessageGetResponseStatusCode(responseMessage));
NSLog(#"CFHTTPMessageCopyResponseStatusLine: %s\n", CFStringGetCStringPtr(CFHTTPMessageCopyResponseStatusLine(responseMessage), kCFStringEncodingUTF8));
NSLog(#"Proxy-Authenticate header value: %s\n", CFStringGetCStringPtr(CFHTTPMessageCopyHeaderFieldValue(responseMessage, CFSTR("Proxy-Authenticate")), kCFStringEncodingUTF8));
CFHTTPAuthenticationRef auth = NULL;
auth = CFHTTPAuthenticationCreateFromResponse(NULL, responseMessage);
NSLog(#"CFHTTPAuthenticationCreateFromResponse:\n"); CFShow(auth);
if (auth) {
CFStreamError error = { 0 };
result = CFHTTPAuthenticationIsValid(auth, &error);
if (!result) {
NSLog(#"CFHTTPAuthenticationIsValid: false, error %d (0x%x), domain %ld (0x%lx)\n", error.error, error.error, error.domain, error.domain);
} else {
NSLog(#"CFHTTPAuthenticationCopyMethod=%s\n", CFStringGetCStringPtr(CFHTTPAuthenticationCopyMethod(auth), kCFStringEncodingUTF8));
NSLog(#"authRealm=%s\n", CFStringGetCStringPtr(CFHTTPAuthenticationCopyRealm(auth), kCFStringEncodingUTF8));
}
};
}
Output:
2015-02-13 11:01:05.654 CFHTTPMessageAppendBytes result: 1
2015-02-13 11:01:05.655 CFHTTPMessageIsHeaderComplete: 1
2015-02-13 11:01:05.656 CFHTTPMessageGetResponseStatusCode: 407
2015-02-13 11:01:05.657 CFHTTPMessageCopyResponseStatusLine: HTTP/1.1 407 Proxy authentication required
2015-02-13 11:01:05.657 Proxy-Authenticate header value: Basic realm="example.com"
2015-02-13 11:01:05.658 CFHTTPAuthenticationCreateFromResponse:
<CFHTTPAuthentication 0x7fe0d36017f0>{state = Failed; scheme = <undecided>, forProxy = true}
2015-02-13 11:01:05.658 CFHTTPAuthenticationIsValid: false, error -1000 (0xfffffc18), domain 4 (0x4)

You encounter same problem as I, but in a bit different flavor.
Problem is that CFHTTPAuthenticationCreateFromResponse works only with response fetched from CFHTTPStream.
I found a solution see my answer.
Expose private API (this will cause that you will fail Apple review).
// exposing private API for workaround
extern void _CFHTTPMessageSetResponseURL(CFHTTPMessageRef, CFURLRef);
Add add URL to response using this private API:
_CFHTTPMessageSetResponseURL(responseMessage,
(__bridge CFURLRef)[NSURL URLWithString: "https://example.com/"]);
auth = CFHTTPAuthenticationCreateFromResponse(NULL, responseMessage);

Related

Checking response code of all URLs in a column [Airtable database]

We have an airtable database of over 24000 records. These records are websites, and many now have errors in them (missing "/", extra space...). We are trying to detect the websites that have these errors before manually fixing them.
What we have tried
So far, we have used the fetch method to call each URL and report back on the error status . This is the script we have used:
const inputConfig = input.config();
const url = inputConfig.url;
let status;
try {
const response = await fetch(url);
status = response.status; } catch (error) {
status = 'error'; }
output.set('status', status);
Issues we ran into
The script won't follow redirects, so it reports "error" back if there is a redirect even if the URL is working.
The output now is either "200" meaning the URL works, or "error". We don't get the actual response code of the error, which we ideally would like to get.
Any help would be appreciated! Thanks
There's some nuance to how fetch works. If you review Mozilla's documentation they say:
The Promise returned from fetch() won't reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, as soon as the server responds with headers, the Promise will resolve normally (with the ok property of the response set to false if the response isn't in the range 200–299), and it will only reject on network failure or if anything prevented the request from completing.
So you have to do an extra check in your code to determine if the request was successful or not and throw your own error. In your case, you don't necessarily need to throw an error at all and can just rely on ok property of the response.
const config = input.config();
const url = config.url;
let status = null;
const response = await fetch(url);
if(response.ok) {
status = response.status
} else {
status = 'error'
}
output.set('status', status);

Solve issue POSTING to webhook for IFTTT from Arduino MKR1010

I am aiming to make a post request to trigger a IFTTT webhook action. I am using the MKR1010 board. I am able to connect to the network and turn the connected LED on and off using the cloud integration.
The code is as follows, but doesn't trigger the web hook. I can manually paste the web address in a browser and this does trigger the web hook. When the code is posted it returns a 400 bad request error.
The key has been replaced in the below code with a dummy value.
Does anybody know why this is not triggering the web hook? / Can you explain why the post request is being rejected by the server? I don't even really need to read the response from the server as long as it is sent.
Thank you
// ArduinoHttpClient - Version: Latest
#include <ArduinoHttpClient.h>
#include "thingProperties.h"
#define LED_PIN 13
#define BTN1 6
char serverAddress[] = "maker.ifttt.com"; // server address
int port = 443;
WiFiClient wifi;
HttpClient client = HttpClient(wifi, serverAddress, port);
// variables will change:
int btnState = 0; // variable for reading the pushbutton status
int btnPrevState = 0;
void setup() {
// Initialize serial and wait for port to open:
Serial.begin(9600);
// This delay gives the chance to wait for a Serial Monitor without blocking if none is found
delay(1500);
// Defined in thingProperties.h
initProperties();
// Connect to Arduino IoT Cloud
ArduinoCloud.begin(ArduinoIoTPreferredConnection);
/*
The following function allows you to obtain more information
related to the state of network and IoT Cloud connection and errors
the higher number the more granular information you’ll get.
The default is 0 (only errors).
Maximum is 4
*/
setDebugMessageLevel(2);
ArduinoCloud.printDebugInfo();
// setup the board devices
pinMode(LED_PIN, OUTPUT);
pinMode(BTN1, INPUT);
}
void loop() {
ArduinoCloud.update();
// Your code here
// read the state of the pushbutton value:
btnState = digitalRead(BTN1);
if (btnPrevState == 0 && btnState == 1) {
led2 = !led2;
postrequest();
}
digitalWrite(LED_PIN, led2);
btnPrevState = btnState;
}
void onLed1Change() {
// Do something
digitalWrite(LED_PIN, led1);
//Serial.print("The light is ");
if (led1) {
Serial.println("The light is ON");
} else {
// Serial.println("OFF");
}
}
void onLed2Change() {
// Do something
digitalWrite(LED_PIN, led2);
}
void postrequest() {
// String("POST /trigger/btn1press/with/key/mykeyhere")
Serial.println("making POST request");
String contentType = "/trigger/btn1press/with/key";
String postData = "mykeyhere";
client.post("/", contentType, postData);
// read the status code and body of the response
int statusCode = client.responseStatusCode();
String response = client.responseBody();
Serial.print("Status code: ");
Serial.println(statusCode);
Serial.print("Response: ");
Serial.println(response);
Serial.println("Wait five seconds");
delay(5000);
}
Why do you want to make a POST request and send the key in the POST body? The browser sends a GET request. It would be
client.get("/trigger/btn1press/with/key/mykeyhere");
In HttpClient post() the first parameter is 'path', the second parameter is contentType (for example "text/plain") and the third parameter is the body of the HTTP POST request.
So your post should look like
client.post("/trigger/btn1press/with/key/mykeyhere", contentType, postData);

Unable to send GET request to Twitter API, but can send POST request?

We use application-only authentication and get back an access token. We then make a GET request to a specific user's timeline by doing the following:
_bpos = snprintf(_buffer, sizeof(_buffer)-1, "GET /1.1/statuses/user_timeline.json?user_id=dog_rates&count=1 HTTP/1.1\n" //This is the HTTP request that gets no reply at all, even though it does send
"Host: api.twitter.com\n"
"Authorization: Bearer %s",authToken);
But somehow this works:
_bpos = snprintf(_buffer, sizeof(_buffer) - 1, "POST /oauth2/token HTTP/1.1\n" //I used this to test to make sure the following read/write works, it did for me
"Host: api.twitter.com\n"
"Authorization: Basic 12312312312345678901234567890\n"
"Content-Type: application/x-www-form-urlencoded;charset=UTF-8\n"
"Content-Length: 29\n\n"
"grant_type=client_credentials");
So it seems that only request of type GET are troublesome. I Any help would be appreciated!
Code
_bpos = snprintf(_buffer, sizeof(_buffer)-1, "GET /1.1/statuses/user_timeline.json?user_id=dog_rates&count=1 HTTP/1.1\n" //This is the HTTP request that gets no reply at all, even though it does send
"Host: api.twitter.com\n"
"Authorization: Bearer %s",authToken);
mbedtls_printf("Buffer content:\n%s\n",_buffer); //prints the content of the buffer to ensure the HTTP request is right
/* Sending REST API GET request */
ret = mbedtls_ssl_write(&_ssl, (const unsigned char *) _buffer, _bpos); //writes the data to the socket
mbedtls_printf("how far u gonna ret: %d\r\n",ret); //prints the return value of the write, positive is number of bytes written (should be equal to _bpos), negative is a failure
if (ret < 0) //Checks to see if write failed
{
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) //If write failed and didnt give back a read/write error, the TLS connection is broken, and the program prints an error
{
print_mbedtls_error("mbedtls_ssl_write", ret);
onError(_tcpsocket, -1 );
}
return -1;
}
/* Read data out of the socket */
usedspace = 0;
bufptr = (unsigned char *) _buffer;//this and the above line are used to concatenate multiple reads into one buffer, however second read is commented out for now.
ret = mbedtls_ssl_read(&_ssl, bufptr, static_cast<size_t>(RECV_BUFFER_SIZE-usedspace)); //read from socket into buffer
mbedtls_printf("how far u gonna ret: %d\r\n",ret);//prints return value of read, same deal as write return value
if(ret < 0)//same check that write does
{
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE)
{
print_mbedtls_error("mbedtls_ssl_read", ret);
onError(_tcpsocket, -1 );
}
delete[] buf;
return -1;
}
mbedtls_printf("read finished\r\n");//placeholder text to see if you get this far
usedspace = usedspace+ret;
bufptr = bufptr + ret;
Your HTTP requests are not valid. You are missing a newline after your last header in the GET request, and you're separating header lines with \n, and not with \r\n (also see this).
I'd suggest you use a library like mbed-http to build your requests for you.

(401) Unauthorized calling https://api.twitter.com/oauth/access_token using tweetinvi

I started playing with tweetinvi to connect to twitter api. I keep getting "The remote server returned an error: (401) Unauthorized." error message when I call CredentialsCreator.GetCredentialsFromVerifierCode() after being redirected.
I added my phone to my account.
I made use the Consumer Key and Consumer Secret are the same.
I made use that my time is current
The callback url in app settings is http://127.zero.zero.1:53260/
I'm kinda of lost on what to do next.
This is the only code that I use:
protected void Page_Load( object sender, EventArgs e )
{
Tweetinvi.WebLogic.TemporaryCredentials applicationCredentials = (Tweetinvi.WebLogic.TemporaryCredentials)CredentialsCreator.GenerateApplicationCredentials( Properties.Settings.Default.TwitterConsumerKey, Properties.Settings.Default.TwitterConsumerSecret );
if (Request["oauth_token"] == null)
{
string url = CredentialsCreator.GetAuthorizationURLForCallback( applicationCredentials, "http://127.0.0.1:53260/twitter.aspx" );
Response.Redirect( url, false );
}
else
{
string verifierCode = Request["oauth_verifier"];
// error calling this code
var newCredentials = CredentialsCreator.GetCredentialsFromVerifierCode( verifierCode, applicationCredentials );
Console.WriteLine( "Access Token = {0}", newCredentials.AccessToken );
Console.WriteLine( "Access Token Secret = {0}", newCredentials.AccessTokenSecret );
}
}
Looks like you have everything correct. It might be the library you are using. Have you tried LinqToTwitter?
They have several examples you can test at http://linqtotwitter.codeplex.com/SourceControl/latest#ReadMe.txt. Just download their source files and you'll find Linq2TwitterDemos_WebForms project that you can text out.

URL encoding in java and decoding in javascript

I have an URL to encode on my java serveur and then to decode with javascript.
I try to retrieve a String I send in param with java. It is an error message from a form validation function.
I do it like that (server side. Worker.doValidateForm() return a String) :
response.sendRedirect(URLEncoder.encode("form.html?" + Worker.doValidateForm(), "ISO-8859-1"));
Then, in my javascript, I do that :
function retrieveParam() {
var error = window.location.search;
decodeURIComponent(error);
if(error)
alert(error);
}
Of course it doesn't work. Not the same encoding I guess.
So my question is : which method can I use in Java to be able to decode my URL with javascript ?
It's ok ! I have found a solution.
Server side with Java :
URI uri = null;
try {
uri = new URI("http", "localhost:8080", "/PrizeWheel/form.html", Worker.doValidateForm(), null);
} catch (URISyntaxException e) {
this.log.error("class Worker / method doPost:", e); // Just writing the error in my log file
}
String url = uri.toASCIIString();
response.sendRedirect(url);
And in the Javascript (function called in the onload of the redirected page) :
function retrieveParam() {
var error = decodeURI(window.location.search).substring(1);
if(error)
alert(error);
}
You don't use URLEncoder to encode URLs, it us used to encode form data to application/x-www-form-urlencoded MIME format. You use URIEncoder instead, see http://contextroot.blogspot.fi/2012/04/encoding-urls-in-java-is-quite-trivial.html

Resources