Search code examples
ccurllibcurlbearer-token

Is there a libcurl curl_easy_setopt() version of curl --data-urlencode


I'm tying to connect to the latest Royal Mail API V4 to get a token Based on existing working code in C and libcurl to connect to V3. I have a working curl example script which gets a token using --data-urlencode which returns a token.

curl -k --location 'https://authentication.proshipping.net/connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=AAAAAAA' \
--data-urlencode 'client_secret=BBBBBBBBB' \
--data-urlencode 'grant_type=client_credentials'

I tried the following in C and libcurl

curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, FALSE);

curl_easy_setopt(hnd, CURLOPT_URL, "https://authentication.proshipping.net/connect/token");
curl_easy_setopt(hnd, CURLOPT_POST, TRUE);

headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded");
curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers);

postfields = curl_slist_append(postfields, "client_id=AAAAAAA");
postfields = curl_slist_append(postfields, "client_secret=BBBBBBBBB");
postfields = curl_slist_append(postfields, "grant_type=client_credentials");
curl_easy_setopt(hnd, CURLOPT_POSTFIELDS, postfields);

curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, writefunc);
curl_easy_setopt(hnd, CURLOPT_WRITEDATA, &s);

res = curl_easy_perform(hnd);

But the libcurl sample returns the output

{"error":"invalid_client"}

Can anyone help with --data-urlencode or equivalent command in libcurl? Thanks


Solution

  • There does not appear to be a single easy option that mimics the full functionality of --data-urlencode, which includes features for loading and encoding files with a special syntax, and also concatenates its arguments to form a single blob.

    The library function used to URL-encode null-terminated byte strings is curl_easy_escape. The resulting string must be passed to curl_free when the user is done with it.

    char *curl_easy_escape(CURL *curl, const char *string, int length);
    void curl_free(void *ptr);
    

    (The internal data_urlencode (implementing --data-urlencode) uses curl_easy_escape.)


    As for CURLOPT_POSTFIELDS,

    CURLcode curl_easy_setopt(CURL *handle, CURLOPT_POSTFIELDS, char *postdata);
    

    the third argument is expected to be a null-terminated byte string (by default; can manually size with CURLOPT_POSTFIELDSIZE) containing the full data to send.

    From your example, --libcurl generates:

    curl_easy_setopt(hnd, CURLOPT_POSTFIELDS, "client_id=AAAAAAA&client_secret=BBBBBBBBB&grant_type=client_credentials");
    

    See also: http-post.c.


    A vague mimicry of --data-urlencode's most basic functionality (the name=content syntax) would be approximately:

    #include <curl/curl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    static int data_append(char **to, const char *name, const char *field)
    {
        /* 1 if initial segment, 0 if concat, justifies & below */
        unsigned init = (NULL == *to);
    
        /* first argument ignored in 7.82.0+, otherwise modify `data_append` */
        char *escaped_field = curl_easy_escape(NULL, field, 0);
    
        if (!escaped_field)
            return 0;
    
        /* previous */
        size_t offset = !init ? strlen(*to) : 0;
        /* name=escaped */
        size_t length = strlen(name) + 1 + strlen(escaped_field);
    
        /* previous&name=escaped\0 */
        char *result = realloc(*to, offset + !init + length + 1);
    
        if (result) {
            sprintf(result + offset, "&%s=%s" + init, name, escaped_field);
            *to = result;
        }
    
        curl_free(escaped_field);
    
        return NULL != result;
    }
    
    int main(void)
    {
        CURL *curl = curl_easy_init();
    
        char *data = NULL;
        data_append(&data, "name", "field");
        data_append(&data, "foo", "hello world!");
        fprintf(stderr, "DEBUG: Sending: <%s>\n", data);
    
        curl_easy_setopt(curl, CURLOPT_URL, "https://mockbin.org/request");
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
    
        curl_easy_perform(curl);
        curl_easy_cleanup(curl);
    
        free(data);
    }
    

    Results:

    ./a.out 
    DEBUG: Sending: <name=field&foo=hello%20world%21>
    ...
    ...
      "postData": {
        "mimeType": "application/x-www-form-urlencoded",
        "text": "name=field&foo=hello%20world%21",
        "params": {
          "name": "field",
          "foo": "hello world!"
        }
      }
    ...
    ...
    

    This is roughly

    $ curl -L 'https://mockbin.org/request' --data-urlencode 'name=field' --data-urlencode 'foo=hello world!'
    

    (--data-urlencode implements the ' ' => '+' conversion described in RFC1866.)


    Aside: there is also CURLOPT_MIMEPOST which can be used for multipart/form-data.