I am trying to develop a desktop application that uses dropbox. I am using C++, libcurl (for the requests) liboauthcpp (for the authentication) and the Rest API (from dropbox).
Since I have successed the authentication and some more operations (list and download file), I cannot manage to upload a file. I am receiving the following error: {"error": "Invalid signature."}
Here is my code:
m_upload_url = "https://api-content.dropbox.com/1/files/sandbox/";
OAuth::Consumer consumer(m_consumer_key, m_consumer_secret);
OAuth::KeyValuePairs access_token_resp_data = OAuth::ParseKeyValuePairs(m_access_token_Response);
OAuth::Token access_token = OAuth::Token::extract( access_token_resp_data );
OAuth::Client oauth = OAuth::Client(&consumer, &access_token);
string oAuthQueryString = oauth.getURLQueryString( OAuth::Http::Post, m_upload_url);
string upload_Request = (string(m_upload_url) + string("?") + oAuthQueryString);
CURL *curl;
CURLcode res;
struct curl_httppost *formpost=NULL;
struct curl_httppost *lastptr=NULL;
struct curl_slist *headerlist=NULL;
static const char buf[] = "Expect:";
curl_global_init(CURL_GLOBAL_ALL);
/* Fill in the file upload field */
curl_formadd(&formpost,
&lastptr,
CURLFORM_COPYNAME, "file",
CURLFORM_FILE, "C:/Users/Desktop/textfile.txt",
CURLFORM_END);
/* Fill in the filename field */
curl_formadd(&formpost,
&lastptr,
CURLFORM_COPYNAME, "name",
CURLFORM_COPYCONTENTS, "textfile",
CURLFORM_END);
/* Fill in the submit field too, even if this is rarely needed */
curl_formadd(&formpost,
&lastptr,
CURLFORM_COPYNAME, "submit",
CURLFORM_COPYCONTENTS, "send",
CURLFORM_END);
curl = curl_easy_init();
/* initalize custom header list (stating that Expect: 100-continue is not
wanted */
headerlist = curl_slist_append(headerlist, buf);
if(curl) {
/* what URL that receives this POST */
curl_easy_setopt(curl, CURLOPT_URL, upload_Request.c_str());
/* only disable 100-continue header if explicitly requested */
curl_easy_setopt(curl, CURLOPT_HTTPPOST, 1L);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunction);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
string response = string(gData);
qDebug() << QString::fromStdString(response);
/* Check for errors */
if(res != CURLE_OK)
qDebug() << "curl_easy_perform() failed: %s\n" << curl_easy_strerror(res);
/* always cleanup */
curl_easy_cleanup(curl);
/* then cleanup the formpost chain */
curl_formfree(formpost);
/* free slist */
curl_slist_free_all (headerlist);
}
and here is the writeFunction:
char gData[1024*1024];
unsigned int gDataLen = 0;
size_t writeFunction( char *ptr, size_t size, size_t nmemb)
{
memcpy( &( gData[gDataLen] ), ptr, (size * nmemb) );
gDataLen += (size * nmemb);
gData[ gDataLen ] = '\0';
return (size * nmemb);
}
Any ideas, please?
EDIT:
Here is the request that the present code produces:
https://api-content.dropbox.com/1/files/sandbox/?oauth_consumer_key=xxxxxxxxxxxxxxx&oauth_nonce=13xxxxxx83cf&oauth_signature=xxxxxx%xxxxxxxxxxxxxxxxxx%xxxxx%xx&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1371107283&oauth_token=xxxxxxxxxxxxxxx&oauth_version=1.0
and the request that I produse with the plaintext method:
https://api-content.dropbox.com/1/files/sandbox/?oauth_version=1.0&oauth_signature_method=PLAINTEXT&oauth_consumer_key=xxxxxxxxxxxxxxx&oauth_token=xxxxxxxxxxxxxxx&oauth_signature=xxxxxxxxxxxxxxx&xxxxxxxxxxxxxxx
Response that O get in both cases:
{"error": "Invalid signature."}
What OAuth signature method are you using? If it's anything but plaintext, then I believe the problem is that you're not passing the body of the HTTP request to getURLQueryString. The body needs to be part of the string that's signed. This explains why you've been able to get a few GET operations to work; those requests don't have a body.
Side note: it looks like you're using the POST form of file upload, which, as the docs say is considerably more complex than PUT. I think your life will be better if you switch. :-) For example, I think the submit field that you're adding will probably cause the call to not work.
EDIT: Here's a full working example using a manually-constructed plaintext signature. Fill in the app key, app secret, token, and token secret, and be sure to have a file called "hello.txt" for this to work:
#include <stdio.h>
#include <curl/curl.h>
#include <iostream>
#include <fstream>
using namespace std;
size_t read_data(void *ptr, size_t size, size_t nmeb, void *stream)
{
return fread(ptr,size,nmeb,(FILE*)stream);
}
int main(void)
{
CURL *curl;
CURLcode res;
FILE * rfp = fopen("hello.txt", "r");
string appkey = "<APP KEY>";
string appsecret = "<APP SECRET>";
string token = "<ACCESS TOKEN>";
string token_secret = "<ACCESS TOKEN SECRET>";
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "https://api-content.dropbox.com/1/files_put/sandbox/hello.txt");
struct curl_slist *headers = NULL;
string header = "Authorization: OAuth oauth_version=\"1.0\", oauth_signature_method=\"PLAINTEXT\", oauth_consumer_key=\"" + appkey + "\", oauth_token=\"" + token + "\", oauth_signature=\"" + appsecret + "&" + token_secret + "\"";
headers = curl_slist_append(headers, header.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_PUT, 1L);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_data);
curl_easy_setopt(curl, CURLOPT_READDATA, rfp);
res = curl_easy_perform(curl);
curl_slist_free_all(headers);
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
curl_easy_cleanup(curl);
}
fclose(rfp);
return 0;
}
Has anyone used liboauthcpp? I suspect that my mistake is in the way I use getURLQueryString.. I am passing the url into this function, but I am not sure if I have to pass the data too.
And unfortunately, there is no documentation for this library.
Related
I have the following ESP32CAM sketch that should take a picture and post it to Clarify:
#include "Arduino.h"
#include "esp_camera.h"
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <base64.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
// Select camera model
//#define CAMERA_MODEL_WROVER_KIT // Has PSRAM
//#define CAMERA_MODEL_ESP_EYE // Has PSRAM
//#define CAMERA_MODEL_M5STACK_PSRAM // Has PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE // Has PSRAM
#define CAMERA_MODEL_AI_THINKER // Has PSRAM
//#define CAMERA_MODEL_TTGO_T_JOURNAL // No PSRAM
//CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
const char* ssid = "mySSID";
const char* password = "myPass";
void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
Serial.println();
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
// if PSRAM IC present, init with UXGA resolution and higher JPEG quality
// for larger pre-allocated frame buffer.
if(psramFound()){
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 10;
config.fb_count = 2;
} else {
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
#if defined(CAMERA_MODEL_ESP_EYE)
pinMode(13, INPUT_PULLUP);
pinMode(14, INPUT_PULLUP);
#endif
// camera init
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
return;
}
#if defined(CAMERA_MODEL_M5STACK_WIDE)
s->set_vflip(s, 1);
s->set_hmirror(s, 1);
#endif
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
classifyImage();
Serial.println("\nSleep....");
esp_deep_sleep_start();
}
void loop(){
}
void classifyImage() {
String response;
// Capture picture
camera_fb_t * fb = NULL;
fb = esp_camera_fb_get();
if(!fb) {
Serial.println("Camera capture failed");
return;
} else {
Serial.println("Camera capture OK");
}
size_t size = fb->len;
String buffer = base64::encode((uint8_t *) fb->buf, fb->len);
String imgPayload = "{\"inputs\": [{ \"data\": {\"image\": {\"base64\": \"" + buffer + "\"}}}]}";
buffer = "";
// Uncomment this if you want to show the payload
Serial.println(imgPayload);
esp_camera_fb_return(fb);
// Generic model
String model_id = "General";
HTTPClient http;
http.begin("https://api.clarifai.com/v2/models/" + model_id + "/outputs");
http.addHeader("Content-Type", "application/json");
http.addHeader("Authorization", "c7f894790533332388e23d4d21278321");
int httpResponseCode = http.POST(imgPayload);
if(httpResponseCode>0){
Serial.print(httpResponseCode);
Serial.print(" Returned String: ");
Serial.println(http.getString());
} else {
Serial.print("POST Error: ");
Serial.print(httpResponseCode);
}
// Parse the json response: Arduino assistant
const int jsonSize = JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(20) + 3*JSON_OBJECT_SIZE(1) + 6*JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(3) + 20*JSON_OBJECT_SIZE(4) + 2*JSON_OBJECT_SIZE(6);
StaticJsonDocument<jsonSize> doc;
// Deserialize the JSON document
DeserializationError error = deserializeJson(doc, response);
// Test if parsing succeeds.
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}
Serial.println(jsonSize);
Serial.println(response);
for (int i=0; i < 10; i++) {
// const name = doc["outputs"][0]["data"]["concepts"][i]["name"];
// const float p = doc["outputs"][0]["data"]["concepts"][i]["value"];
const char* name = doc["outputs"][0]["data"]["concepts"][i]["name"];
const char* p = doc["outputs"][0]["data"]["concepts"][i]["value"];
Serial.println("=====================");
Serial.print("Name:");
Serial.println(name[i]);
Serial.print("Prob:");
Serial.println(p);
Serial.println();
}
}
It posts the image to Clarifai bit what I get in return is:
-400 Returned String: {"status":{"code":11102,"description":"Invalid request","details":"Empty or malformed authorization header. Please provide an API key or session token.","req_id":"39d7b4f1b7ad489fb3a9a878000f6e88"},"outputs":[]}
-deserializeJson() failed: EmptyInput
What I need is to confirm if the HTTP POST request is formatted properly.
This problem is not the formatting of your POST request, it's the fact that your authorization header is incorrect (as the error "Empty or malformed authorization header" indicates).
As the Clarafai documentation indicates, the Authorization header should be:
Authorization: Key YOUR_API_KEY
your code is sending
Authorization: YOUR_API_KEY
change the line that sets the Authorization header to have the "Key " before the API key.
Given that the ESP32 is a fussy environment where a lot can go wrong with an HTTP request, a good way to debug these problems is to use the curl utility to attempt the same operation in a more full-featured environment. In this case on a Mac or Linux machine you could run
curl -X POST -F filename -H 'Authorization: YOUR_API_KEY' -H 'Content-type: application/json' https://api.clarifai.com/v2/models/MODEL_ID/outputs
where the photo you're testing with is stored in filename. The you can be sure the POST request is correct and work out what other things might be wrong.
Also it appears that you may have posted your API key to the Internet. If that's the case, I'd recommend invalidating the one in the code you posted and generating a new one.
I'm getting the following response and I don't know how to identify the problem. Fiddler doesn't capture anything so I believe the request is not being sent out.
HTTP/1.1 411 Length Required
Content-Type: text/html; charset=us-ascii
Server: Microsoft-HTTPAPI/2.0
Date: Wed, 22 May 2019 11:15:04 GMT
Connection: close
Content-Length: 344
I've attempted to follow the other examples I've found, but it seems setting the body no longer compiles.
// error C2679: binary '=': no operator found which takes a right-hand operand of type 'const char *' (or there is no acceptable conversion)
req_.body() = "test";
I'm using Visual Studio 2017 compiled for x64 with Boost linked as DLLs. I started with the beast samples and got "GET" to work perfectly for me. I'm having problems getting "POST" to work from the beast client
`
//
// Example: HTTP client, asynchronous
//
// Quickly add boost DLLs with: https://www.nuget.org/packages/boost-vc141/
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/strand.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/lexical_cast.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
// Report a failure
void
fail(beast::error_code ec, char const* what)
{
std::cerr << what << ": " << ec.message() << "\n";
}
// Performs an HTTP GET and prints the response
class session : public std::enable_shared_from_this<session>
{
tcp::resolver resolver_;
beast::tcp_stream stream_;
beast::flat_buffer buffer_; // (Must persist between reads)
http::request<http::dynamic_body> req_;
http::response<http::string_body> res_;
public:
// Objects are constructed with a strand to
// ensure that handlers do not execute concurrently.
explicit
session(net::io_context& ioc)
: resolver_(net::make_strand(ioc))
, stream_(net::make_strand(ioc))
{
}
// Start the asynchronous operation
void
run(
char const* host,
char const* port,
char const* target,
char const* body,
int version)
{
// Set up an HTTP POST request message
req_.version(version);
req_.method(http::verb::post);
req_.target(target);
req_.set(http::field::host, host);
req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
req_.set(http::field::content_length, boost::lexical_cast<std::string>(strlen(body)));
req_.set(http::field::body, body);
req_.prepare_payload();
// following line doesn't compile:
// error C2679: binary '=': no operator found which takes a right-hand operand of type 'const char *' (or there is no acceptable conversion)
//req_.body() = body;
// Look up the domain name
resolver_.async_resolve(
host,
port,
beast::bind_front_handler(
&session::on_resolve,
shared_from_this()));
}
void
on_resolve(
beast::error_code ec,
tcp::resolver::results_type results)
{
if (ec)
return fail(ec, "resolve");
// Set a timeout on the operation
stream_.expires_after(std::chrono::seconds(30));
// Make the connection on the IP address we get from a lookup
stream_.async_connect(
results,
beast::bind_front_handler(
&session::on_connect,
shared_from_this()));
}
void
on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type)
{
if (ec)
return fail(ec, "connect");
// Set a timeout on the operation
stream_.expires_after(std::chrono::seconds(30));
// Send the HTTP request to the remote host
http::async_write(stream_, req_,
beast::bind_front_handler(
&session::on_write,
shared_from_this()));
}
void
on_write(
beast::error_code ec,
std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
if (ec)
return fail(ec, "write");
// Receive the HTTP response
http::async_read(stream_, buffer_, res_,
beast::bind_front_handler(
&session::on_read,
shared_from_this()));
}
void
on_read(
beast::error_code ec,
std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
if (ec)
return fail(ec, "read");
// Write the message to standard out
std::cout << res_ << std::endl;
// Gracefully close the socket
stream_.socket().shutdown(tcp::socket::shutdown_both, ec);
// not_connected happens sometimes so don't bother reporting it.
if (ec && ec != beast::errc::not_connected)
return fail(ec, "shutdown");
// If we get here then the connection is closed gracefully
}
};
std::string create_body()
{
boost::property_tree::ptree tree;
tree.put("foo", "bar");
std::basic_stringstream<char> jsonStream;
boost::property_tree::json_parser::write_json(jsonStream, tree, false);
return jsonStream.str();
}
int main(int argc, char** argv)
{
// Check command line arguments.
if (argc != 4 && argc != 5)
{
std::cerr <<
"Usage: http-client-async <host> <port> <target> [<HTTP version: 1.0 or 1.1(default)>]\n" <<
"Example:\n" <<
" http-client-async www.example.com 80 /\n" <<
" http-client-async www.example.com 80 / 1.0\n";
return EXIT_FAILURE;
}
auto const host = argv[1];
auto const port = argv[2];
auto const target = argv[3];
int version = argc == 5 && !std::strcmp("1.0", argv[4]) ? 10 : 11;
// The io_context is required for all I/O
net::io_context ioc;
// Launch the asynchronous operation
std::make_shared<session>(ioc)->run(host, port, target, create_body().c_str(), version);
// Run the I/O service. The call will return when
// the get operation is complete.
ioc.run();
return EXIT_SUCCESS;
}
`
How do I see what beast is generating as the packet to be sent?
How do I fix the compilation error attempting to set the body as shown in other posts: request.body() = "bodytext";
Can someone provide a sample beast client & server that uses post?
In the operator= for the body is not available because your request is declared with template http::dynamic_body:
http::request<http::dynamic_body> req_;
Change your template argument to http::string_body and the operator= will work
http::response<http::string_body> req_;
It will be possible to compile the code
req_.body() = body;
I tested it under CentOS7.
I am searching for c code for http-webdav client using the CURL library with authentication enabled.
I have tried with some of the sample codes provided by the CURL , but nothing seem to pass the authentication methods and work.
I have enabled the digest authentication with the user and password.
The client request is sent correctly but the server response is 301.
I have captured the packets using the wireshark.
Is there any thing that is missing?
Below is the code I have tried.
/* read callback function, fread() look alike */
static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *stream)
{
ssize_t retcode;
curl_off_t nread;
int *fdp = (int *)stream;
int fd = *fdp;
retcode = read(fd, ptr, size * nmemb);
nread = (curl_off_t)retcode;
fprintf(stderr, "*** We read %" CURL_FORMAT_CURL_OFF_T
" bytes from file\n", nread);
return retcode;
}
/*
* This example shows a HTTP PUT operation. PUTs a file given as a command
* line argument to the URL also given on the command line.
*
* This example also uses its own read callback.
*
* Here's an article on how to setup a PUT handler for Apache:
* http://www.apacheweek.com/features/put
*/
int curlApache ()
{
CURL *curl;
CURLcode res;
FILE * hd_src;
struct stat file_info;
char *file;
char *url;
char error;
file = "/bd0/filecreate.txt";
url = "http://10.1.20.127/sites/";
//url = "http://10.1.21.14/webdav/test.txt";
struct curl_slist *slist = NULL;
slist = curl_slist_append(slist, "Accept: text/xml");
slist = curl_slist_append(slist, "Depth: infinity");
slist = curl_slist_append(slist, "Connection: Keep-Alive");
slist = curl_slist_append(slist, "Content-Type: text/xml");
slist = curl_slist_append(slist, "Expect:");
/* get the file size of the local file */
stat(file, &file_info);
hd_src = fopen(file, "a+");
if (hd_src == NULL)
printf("Disc full or no permission\n");
const char *str = "This is the file content";
const char read[24];
if (hd_src != NULL)
if (fputs (str, hd_src) != EOF);
if( fgets (read, 24, hd_src)!=NULL ) {
/* writing content to stdout */
puts(read);
}
/* In windows, this will init the winsock stuff */
curl_global_init(CURL_GLOBAL_ALL);
/* get a curl handle */
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_VERBOSE, 3L);
/* we want to use our own read function */
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);
/* enable uploading */
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
/* HTTP PUT please */
curl_easy_setopt(curl, CURLOPT_PUT, 1L);
/* tell libcurl we can use "any" auth, which lets the lib pick one, but it
also costs one extra round-trip and possibly sending of all the PUT
data twice!!! */
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, (long)CURLAUTH_DIGEST);
curl_easy_setopt(curl, CURLOPT_USERPWD, "admin:nimo0630");
fseek(hd_src, 0L, SEEK_END);
int file_size;
file_size = ftell(hd_src);
Curl_setopt(curl, CURLOPT_INFILE, hd_src);
Curl_setopt(curl, CURLOPT_INFILESIZE, file_size);
/* specify target URL, and note that this URL should include a file
name, not only a directory */
curl_easy_setopt(curl, CURLOPT_URL, url);
/* now specify which file to upload */
curl_easy_setopt(curl, CURLOPT_READDATA, hd_src);
/* provide the size of the upload, we specicially typecast the value
to curl_off_t since we must be sure to use the correct data size */
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size);
/* Now run off and do what you've been told! */
res = curl_easy_perform(curl);
/* Check for errors */
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
if(!res) {
/* extract the available authentication types */
long auth;
res = curl_easy_getinfo(curl, CURLINFO_HTTPAUTH_AVAIL, &auth);
if(!res)
{
if(!auth)
printf("No auth available, perhaps no 401?\n");
else
{
printf("%s%s%s%s\n", \
auth & CURLAUTH_BASIC ? "Basic ":"", \
auth & CURLAUTH_DIGEST ? "Digest ":"", \
auth & CURLAUTH_NEGOTIATE ? "Negotiate ":"", \
auth % CURLAUTH_NTLM ? "NTLM ":"");
}
}
}
/* always cleanup */
curl_easy_cleanup(curl);
}
fclose(hd_src); /* close the local file */
curl_global_cleanup();
return 0;
}
I am using OAuth2 with a server, and wininet as the http client. I cannot see how to set wininet to use a bearer token (just see how to set username and password for basic authentication). Is it possible to tell wininet to use a bearer token? (something like INTERNET_OPTION_USERNAME...)
This is same code to get access token from google. Note InternetOpenA uses NULL for username and password. If successful it reads a JSON file in to str which will contain access token, this access token is used with subsequent GET/POST requests.
std::string client_secret = "123";
std::string client_id = "1234";
std::string authorization_token = "token from browser";
std::string query, header;
{
stringstream ss;
ss << "code=" << authorization_token
<< "&client_id=" << client_id << ".apps.googleusercontent.com"
<< "&client_secret=" << client_secret
<< "&redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=authorization_code";
query = ss.str();
}
{
stringstream ss;
ss << "Content-Type: application/x-www-form-urlencoded\r\nContent-length: "
<< query.size() << "\r\nConnection: Close\r\n\r\n";
header = ss.str();
}
HINTERNET hopen = InternetOpenA("appname", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
HINTERNET hconnect = InternetConnectA(hopen,
"accounts.google.com", INTERNET_DEFAULT_HTTPS_PORT,
NULL, //username
NULL, //password
INTERNET_SERVICE_HTTP, 0, 1);
HINTERNET hrequest = HttpOpenRequestA(hconnect, "POST", "o/oauth2/token",
NULL, "http://localhost",
NULL, INTERNET_FLAG_SECURE | INTERNET_FLAG_DONT_CACHE, 0);
if(hrequest)
{
BOOL res = HttpSendRequestA(hrequest, header.c_str(), header.size(),
&query[0], query.size());
std::string data;
if(res)
{
DWORD received;
const int bufsize = 4096;
std::vector<BYTE> buf(bufsize);
while(InternetReadFile(hrequest, &buf[0], bufsize, &received) && received)
data.insert(data.end(), buf.begin(), buf.begin() + received);
std::string str(data.begin(), data.end());
//=> access token
}
InternetCloseHandle(hrequest);
}
InternetCloseHandle(hconnect);
InternetCloseHandle(hopen);
Pass "Authorization: Bearer " + the token to HttpAddRequestHeaders(). In my case I had to access an IdentityServer API from a win32 C++ MFC (WinInet) Client. Make sure to set InternetConnect() with the server domain instead of an IP address and that the token has correct scopes.
HINTERNET hSession, hConnect, hRequest;
hSession = InternetOpen( NULL, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0 );
if( hSession )
{
CString strServerDomain = GetServerDomainName();
hConnect = InternetConnect( hSession, strServerDomain, INTERNET_DEFAULT_HTTPS_PORT,
NULL, NULL,INTERNET_SERVICE_HTTP, 0, 0 );
if( hConnect )
{
hRequest = HttpOpenRequest( hConnect, "GET", strParams, NULL, NULL, 0,
INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_SECURE, 0 ); // strParams = "projectName/controller/Action/"
if( hRequest )
{
CString strToken = "Your Token Goes Here"; // Token
CString strHeader = "";
strHeader.Format( "Authorization: Bearer %s", strToken );
BOOL bHeaderAdded = HttpAddRequestHeaders( hRequest, strHeader, strHeader.GetLength(), 0 );
ASSERT( bHeaderAdded );
BOOL hHttpRequest = HttpSendRequest( hRequest, NULL, 0, NULL, 0 );
I have been trying to create a source client for ice cast for ios. I have been able to connect using asyncsocket to connect to the socket. I am also able to write data to the server. The icecast configuration is done for mp3 format. But the mp3 file written to the server is corrupt. I am providing some code snippets.
Header:
NSString *string = #"SOURCE /sync HTTP/1.0\r\n"
"Authorization: Basic c291cmNlOmhhY2ttZQ==\r\n"
"User-Agent: butt-0.1.12\r\n"
"User-Agent: butt-0.1.12\r\n"
"content-type: audio/mpeg\r\n"
"ice-name: sync's Stream\r\n"
"ice-public: 0\r\n"
"ice-genre: Rock\r\n"
"ice-description: This is my server description\r\n"
"Connection: keep-alive\r\n"
"ice-audio-info: ice-samplerate=44100;ice-bitrate=48;ice-channels=2\r\n\r\n";
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
//sending http request to write the header
NSLog(#"Sending HTTP Request.");
[socket writeData:data withTimeout:-1 tag:1];
//write buffer data to server
[socket writeData:self.dataBuffer withTimeout:-1 tag:1];
for recording i am using aqrecorder using the following code to record it.
void AQRecorder::MyInputBufferHandler( void * inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp * inStartTime,
UInt32 inNumPackets,
const AudioStreamPacketDescription* inPacketDesc)
{
AQRecorder *aqr = (AQRecorder *)inUserData;
try {
if (inNumPackets > 0) {
// write packets to file
XThrowIfError(AudioFileWritePackets(aqr->mRecordFile, FALSE, inBuffer->mAudioDataByteSize,
inPacketDesc, aqr->mRecordPacket, &inNumPackets, inBuffer->mAudioData),
"AudioFileWritePackets failed");
aqr->mRecordPacket += inNumPackets;
NSLog(#"size = %u",(unsigned int)inBuffer->mAudioDataByteSize);
data = [[[NSData alloc]initWithBytes:inBuffer->mAudioData length:inBuffer->mAudioDataByteSize]retain];
server *srv = [[server alloc]init];
srv.dataBuffer=data;
[srv connecting];
}
// if we're not stopping, re-enqueue the buffe so that it gets filled again
if (aqr->IsRunning())
XThrowIfError(AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL), "AudioQueueEnqueueBuffer failed");
} catch (CAXException e) {
char buf[256];
fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf));
}
}
void AQRecorder::StartRecord(CFStringRef inRecordFile)
{
// server *srv=[[server alloc]init];
// [srv connecting];
int i, bufferByteSize;
UInt32 size;
CFURLRef url = nil;
try {
mFileName = CFStringCreateCopy(kCFAllocatorDefault, inRecordFile);
// // specify the recording format
// SetupAudioFormat(kAudioFormatMPEG4AAC);
// specify the recording format, use hardware AAC if available
// otherwise use IMA4
if(IsAACHardwareEncoderAvailable())
SetupAudioFormat(kAudioFormatMPEG4AAC);
else
SetupAudioFormat(kAudioFormatAppleIMA4);
// create the queue
XThrowIfError(AudioQueueNewInput(
&mRecordFormat,
MyInputBufferHandler,
this /* userData */,
NULL /* run loop */, NULL /* run loop mode */,
0 /* flags */, &mQueue), "AudioQueueNewInput failed");
// get the record format back from the queue's audio converter --
// the file may require a more specific stream description than was necessary to create the encoder.
mRecordPacket = 0;
size = sizeof(mRecordFormat);
XThrowIfError(AudioQueueGetProperty(mQueue, kAudioQueueProperty_StreamDescription,
&mRecordFormat, &size), "couldn't get queue's format");
NSString *recordFile = [NSTemporaryDirectory() stringByAppendingPathComponent: (NSString*)inRecordFile];
//url = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef)recordFile, NULL);
url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)recordFile, kCFURLPOSIXPathStyle, false);
// create the audio file
OSStatus status = AudioFileCreateWithURL(url, kAudioFileCAFType, &mRecordFormat, kAudioFileFlags_EraseFile, &mRecordFile);
CFRelease(url);
XThrowIfError(status, "AudioFileCreateWithURL failed");
// copy the cookie first to give the file object as much info as we can about the data going in
// not necessary for pcm, but required for some compressed audio
CopyEncoderCookieToFile();
// allocate and enqueue buffers
bufferByteSize = ComputeRecordBufferSize(&mRecordFormat, kBufferDurationSeconds); // enough bytes for half a second
for (i = 0; i < kNumberRecordBuffers; ++i) {
XThrowIfError(AudioQueueAllocateBuffer(mQueue, bufferByteSize, &mBuffers[i]),
"AudioQueueAllocateBuffer failed");
XThrowIfError(AudioQueueEnqueueBuffer(mQueue, mBuffers[i], 0, NULL),
"AudioQueueEnqueueBuffer failed");
}
// start the queue
mIsRunning = true;
XThrowIfError(AudioQueueStart(mQueue, NULL), "AudioQueueStart failed");
}
catch (CAXException e) {
char buf[256];
fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf));
}
catch (...) {
fprintf(stderr, "An unknown error occurred\n");;
}
}
Do i need to change the format to write to the server?
You're not sending MP3 data, you're sending AAC or M4A data. I don't believe Icecast supports M4A. Are you actually using Icecast or some other server?
For AAC, your Content-Type header is wrong. Try audio/aac, audio/aacp, audio/mp4 or audio/mpeg4-generic.
Also, you only need one User-Agent header, and you should pick something that matches the software you are writing rather than copying someone else's. In the future, there might need to be an adjustment of protocol for your code, and that would only be possible if you used your own user-agent string.