aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRomain Forlot <romain.forlot@iot.bzh>2018-04-03 15:47:42 +0200
committerRomain Forlot <romain.forlot@iot.bzh>2018-12-13 14:12:02 +0100
commitf0fce75e1eb1afb9e1567bfef0907a86c5acf24e (patch)
treef81f49d4ebacdf86e32ef2bf1bba0c7a81ba8b3a
parenta49408d495a7808d8fe4c76b1d5141c58eb9094b (diff)
Adds a CURL wrapper library to help using libcurl
Change-Id: If73bab16a5d4a5258f730c599630bd5fa8e5684f Signed-off-by: Romain Forlot <romain.forlot@iot.bzh>
-rw-r--r--CMakeLists.txt2
-rw-r--r--curl-wrap.c210
-rw-r--r--curl-wrap.h45
-rw-r--r--escape.c428
-rw-r--r--escape.h23
5 files changed, 707 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fe260fa..c294717 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -23,7 +23,7 @@
PROJECT_TARGET_ADD(afb-utilities)
# Define targets
- ADD_LIBRARY(${TARGET_NAME} STATIC wrap-json.c filescan-utils.c)
+ ADD_LIBRARY(${TARGET_NAME} STATIC curl-wrap.c escape.c wrap-json.c filescan-utils.c)
# Library properties
SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
diff --git a/curl-wrap.c b/curl-wrap.c
new file mode 100644
index 0000000..3c566a3
--- /dev/null
+++ b/curl-wrap.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2015, 2016 "IoT.bzh"
+ * Author: José Bollo <jose.bollo@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define _GNU_SOURCE
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <curl/curl.h>
+
+#include "curl-wrap.h"
+#include "escape.h"
+
+
+/* internal representation of buffers */
+struct buffer {
+ size_t size;
+ char *data;
+};
+
+/* write callback for filling buffers with the response */
+static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+ struct buffer *buffer = userdata;
+ size_t sz = size * nmemb;
+ size_t old_size = buffer->size;
+ size_t new_size = old_size + sz;
+ char *data = realloc(buffer->data, new_size + 1);
+ if (!data)
+ return 0;
+ memcpy(&data[old_size], ptr, sz);
+ data[new_size] = 0;
+ buffer->size = new_size;
+ buffer->data = data;
+ return sz;
+}
+
+/*
+ * Perform the CURL operation for 'curl' and put the result in
+ * memory. If 'result' isn't NULL it receives the returned content
+ * that then must be freed. If 'size' isn't NULL, it receives the
+ * size of the returned content. Note that if not NULL, the real
+ * content is one byte greater than the read size and the last byte
+ * zero. This facility allows to handle the returned content as a
+ * null terminated C-string.
+ */
+int curl_wrap_perform(CURL *curl, char **result, size_t *size)
+{
+ int rc;
+ struct buffer buffer;
+ CURLcode code;
+
+ /* init tthe buffer */
+ buffer.size = 0;
+ buffer.data = NULL;
+
+ /* Perform the request, res will get the return code */
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
+
+ /* Perform the request, res will get the return code */
+ code = curl_easy_perform(curl);
+ rc = code == CURLE_OK;
+
+ /* Check for no errors */
+ if (rc) {
+ /* no error */
+ if (size)
+ *size = buffer.size;
+ if (result)
+ *result = buffer.data;
+ else
+ free(buffer.data);
+ } else {
+ /* had error */
+ if (size)
+ *size = 0;
+ if (result)
+ *result = NULL;
+ free(buffer.data);
+ }
+
+ return rc;
+}
+
+void curl_wrap_do(CURL *curl, void (*callback)(void *closure, int status, CURL *curl, const char *result, size_t size), void *closure)
+{
+ int rc;
+ char *result;
+ size_t size;
+ char errbuf[CURL_ERROR_SIZE];
+
+ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
+ rc = curl_wrap_perform(curl, &result, &size);
+ if (rc)
+ callback(closure, rc, curl, result, size);
+ else
+ callback(closure, rc, curl, errbuf, 0);
+ free(result);
+ curl_easy_cleanup(curl);
+}
+
+int curl_wrap_content_type_is(CURL *curl, const char *value)
+{
+ char *actual;
+ CURLcode code;
+
+ code = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &actual);
+ if (code != CURLE_OK || !actual)
+ return 0;
+
+ return !strncasecmp(actual, value, strcspn(actual, "; "));
+}
+
+CURL *curl_wrap_prepare_get_url(const char *url)
+{
+ CURL *curl;
+ CURLcode code;
+
+ curl = curl_easy_init();
+ if(curl) {
+ code = curl_easy_setopt(curl, CURLOPT_URL, url);
+ if (code == CURLE_OK)
+ return curl;
+ curl_easy_cleanup(curl);
+ }
+ return NULL;
+}
+
+CURL *curl_wrap_prepare_get(const char *base, const char *path, const char * const *args)
+{
+ CURL *res;
+ char *url;
+
+ url = escape_url(base, path, args, NULL);
+ res = url ? curl_wrap_prepare_get_url(url) : NULL;
+ free(url);
+ return res;
+}
+
+int curl_wrap_add_header(CURL *curl, const char *header)
+{
+ int rc;
+ struct curl_slist *list;
+
+ list = curl_slist_append(NULL, header);
+ rc = list ? curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list) == CURLE_OK : 0;
+/*
+ curl_slist_free_all(list);
+*/
+ return rc;
+}
+
+int curl_wrap_add_header_value(CURL *curl, const char *name, const char *value)
+{
+ char *h;
+ int rc;
+
+ rc = asprintf(&h, "%s: %s", name, value);
+ rc = rc < 0 ? 0 : curl_wrap_add_header(curl, h);
+ free(h);
+ return rc;
+}
+
+
+CURL *curl_wrap_prepare_post_url_data(const char *url, const char *datatype, const char *data, size_t szdata)
+{
+ CURL *curl;
+
+ curl = curl_easy_init();
+ if (curl
+ && CURLE_OK == curl_easy_setopt(curl, CURLOPT_URL, url)
+ && (!szdata || CURLE_OK == curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, szdata))
+ && CURLE_OK == curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data)
+ && (!datatype || curl_wrap_add_header_value(curl, "content-type", datatype)))
+ return curl;
+ curl_easy_cleanup(curl);
+ return NULL;
+}
+
+CURL *curl_wrap_prepare_post(const char *base, const char *path, const char * const *args)
+{
+ CURL *res;
+ char *url;
+ char *data;
+ size_t szdata;
+
+ url = escape_url(base, path, NULL, NULL);
+ data = escape_args(args, &szdata);
+ res = url ? curl_wrap_prepare_post_url_data(url, NULL, data, szdata) : NULL;
+ free(url);
+ return res;
+}
+
+/* vim: set colorcolumn=80: */
diff --git a/curl-wrap.h b/curl-wrap.h
new file mode 100644
index 0000000..2e44f47
--- /dev/null
+++ b/curl-wrap.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015, 2016 "IoT.bzh"
+ * Author: José Bollo <jose.bollo@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#pragma once
+
+#include <curl/curl.h>
+
+extern char *curl_wrap_url (const char *base, const char *path,
+ const char *const *query, size_t * size);
+
+extern int curl_wrap_perform (CURL * curl, char **result, size_t * size);
+
+extern void curl_wrap_do(CURL *curl, void (*callback)(void *closure, int status, CURL *curl, const char *result, size_t size), void *closure);
+
+extern int curl_wrap_content_type_is (CURL * curl, const char *value);
+
+extern CURL *curl_wrap_prepare_get_url(const char *url);
+
+extern CURL *curl_wrap_prepare_get(const char *base, const char *path, const char * const *args);
+
+extern CURL *curl_wrap_prepare_post_url_data(const char *url, const char *datatype, const char *data, size_t szdata);
+
+extern CURL *curl_wrap_prepare_post(const char *base, const char *path, const char * const *args);
+
+extern int curl_wrap_add_header(CURL *curl, const char *header);
+
+extern int curl_wrap_add_header_value(CURL *curl, const char *name, const char *value);
+
+/* vim: set colorcolumn=80: */
+
diff --git a/escape.c b/escape.c
new file mode 100644
index 0000000..3bb25c2
--- /dev/null
+++ b/escape.c
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2015, 2016 "IoT.bzh"
+ * Author: José Bollo <jose.bollo@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define _GNU_SOURCE
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * Test if 'c' is to be escaped or not.
+ * Any character that is not in [-.0-9A-Z_a-z~]
+ * must be escaped.
+ * Note that space versus + is not managed here.
+ */
+static inline int should_escape(char c)
+{
+/* ASCII CODES OF UNESCAPED CHARS
+car hx/oct idx
+'-' 2d/055 1
+'.' 2e/056 2
+'0' 30/060 3
+... ..
+'9' 39/071 12
+'A' 41/101 13
+... ..
+'Z' 5a/132 38
+'_' 5f/137 39
+'a' 61/141 40
+... ..
+'z' 7a/172 65
+'~' 7e/176 66
+*/
+ /* [-.0-9A-Z_a-z~] */
+ if (c <= 'Z') {
+ /* [-.0-9A-Z] */
+ if (c < '0') {
+ /* [-.] */
+ return c != '-' && c != '.';
+ } else {
+ /* [0-9A-Z] */
+ return c < 'A' && c > '9';
+ }
+ } else {
+ /* [_a-z~] */
+ if (c <= 'z') {
+ /* [_a-z] */
+ return c < 'a' && c != '_';
+ } else {
+ /* [~] */
+ return c != '~';
+ }
+ }
+}
+
+/*
+ * returns the ASCII char for the hexadecimal
+ * digit of the binary value 'f'.
+ * returns 0 if f isn't in [0 ... 15].
+ */
+static inline char bin2hex(int f)
+{
+ if ((f & 15) != f)
+ f = 0;
+ else if (f < 10)
+ f += '0';
+ else
+ f += 'A' - 10;
+ return (char)f;
+}
+
+/*
+ * returns the binary value for the hexadecimal
+ * digit whose char is 'c'.
+ * returns -1 if c isn't i n[0-9A-Fa-f]
+ */
+static inline int hex2bin(char c)
+{
+ /* [0-9A-Fa-f] */
+ if (c <= 'F') {
+ /* [0-9A-F] */
+ if (c <= '9') {
+ /* [0-9] */
+ if (c >= '0') {
+ return (int)(c - '0');
+ }
+ } else if (c >= 'A') {
+ /* [A-F] */
+ return (int)(c - ('A' - 10));
+ }
+ } else {
+ /* [a-f] */
+ if (c >= 'a' && c <= 'f') {
+ return (int)(c - ('a' - 10));
+ }
+ }
+ return -1;
+}
+
+/*
+ * returns the length that will have the text 'itext' of length
+ * 'ilen' when escaped. When 'ilen' == 0, strlen is used to
+ * compute the size of 'itext'.
+ */
+static size_t escaped_length(const char *itext, size_t ilen)
+{
+ char c;
+ size_t i, r;
+
+ if (!ilen)
+ ilen = strlen(itext);
+ c = itext[i = r = 0];
+ while (i < ilen) {
+ r += c != ' ' && should_escape(c) ? 3 : 1;
+ c = itext[++i];
+ }
+ return r;
+}
+
+/*
+ * Escapes the text 'itext' of length 'ilen'.
+ * When 'ilen' == 0, strlen is used to compute the size of 'itext'.
+ * The escaped text is put in 'otext' of length 'olen'.
+ * Returns the length of the escaped text (it can be greater than 'olen').
+ * When 'olen' is greater than the needed length, an extra null terminator
+ * is appened to the escaped string.
+ */
+static size_t escape_to(const char *itext, size_t ilen, char *otext, size_t olen)
+{
+ char c;
+ size_t i, r;
+
+ if (!ilen)
+ ilen = strlen(itext);
+ c = itext[i = r = 0];
+ while (i < ilen) {
+ if (c == ' ')
+ c = '+';
+ else if (should_escape(c)) {
+ if (r < olen)
+ otext[r] = '%';
+ r++;
+ if (r < olen)
+ otext[r] = bin2hex((c >> 4) & 15);
+ r++;
+ c = bin2hex(c & 15);
+ }
+ if (r < olen)
+ otext[r] = c;
+ r++;
+ c = itext[++i];
+ }
+ if (r < olen)
+ otext[r] = 0;
+ return r;
+}
+
+/*
+ * returns the length of 'itext' of length 'ilen' that can be unescaped.
+ * compute the size of 'itext' when 'ilen' == 0.
+ */
+static size_t unescapable_length(const char *itext, size_t ilen)
+{
+ char c;
+ size_t i;
+
+ c = itext[i = 0];
+ while (i < ilen) {
+ if (c != '%')
+ i++;
+ else {
+ if (i + 3 > ilen
+ || hex2bin(itext[i + 1]) < 0
+ || hex2bin(itext[i + 2]) < 0)
+ break;
+ i += 3;
+ }
+ c = itext[i];
+ }
+ return i;
+}
+
+/*
+ * returns the length that will have the text 'itext' of length
+ * 'ilen' when escaped. When 'ilen' == 0, strlen is used to
+ * compute the size of 'itext'.
+ */
+static size_t unescaped_length(const char *itext, size_t ilen)
+{
+ char c;
+ size_t i, r;
+
+ c = itext[i = r = 0];
+ while (i < ilen) {
+ i += (size_t)(1 + ((c == '%') << 1));
+ r++;
+ c = itext[i];
+ }
+ return r;
+}
+
+static size_t unescape_to(const char *itext, size_t ilen, char *otext, size_t olen)
+{
+ char c;
+ size_t i, r;
+ int h, l;
+
+ ilen = unescapable_length(itext, ilen);
+ c = itext[i = r = 0];
+ while (i < ilen) {
+ if (c != '%') {
+ if (c == '+')
+ c = ' ';
+ i++;
+ } else {
+ if (i + 2 >= ilen)
+ break;
+ h = hex2bin(itext[i + 1]);
+ l = hex2bin(itext[i + 2]);
+ c = (char)((h << 4) | l);
+ i += 3;
+ }
+ if (r < olen)
+ otext[r] = c;
+ r++;
+ c = itext[i];
+ }
+ if (r < olen)
+ otext[r] = 0;
+ return r;
+}
+
+/* create an url */
+char *escape_url(const char *base, const char *path, const char * const *args, size_t *length)
+{
+ int i;
+ size_t lb, lp, lq, l, L;
+ const char *null;
+ char *result;
+
+ /* ensure args */
+ if (!args) {
+ null = NULL;
+ args = &null;
+ }
+
+ /* compute lengths */
+ lb = base ? strlen(base) : 0;
+ lp = path ? strlen(path) : 0;
+ lq = 0;
+ i = 0;
+ while (args[i]) {
+ lq += 1 + escaped_length(args[i], strlen(args[i]));
+ i++;
+ if (args[i])
+ lq += 1 + escaped_length(args[i], strlen(args[i]));
+ i++;
+ }
+
+ /* allocation */
+ L = lb + lp + lq + 1;
+ result = malloc(L + 1);
+ if (result) {
+ /* make the resulting url */
+ l = lb;
+ if (lb) {
+ memcpy(result, base, lb);
+ if (result[l - 1] != '/' && path && path[0] != '/')
+ result[l++] = '/';
+ }
+ if (lp) {
+ memcpy(result + l, path, lp);
+ l += lp;
+ }
+ i = 0;
+ while (args[i]) {
+ if (i) {
+ result[l++] = '&';
+ } else if (base || path) {
+ result[l] = memchr(result, '?', l) ? '&' : '?';
+ l++;
+ }
+ l += escape_to(args[i], strlen(args[i]), result + l, L - l);
+ i++;
+ if (args[i]) {
+ result[l++] = '=';
+ l += escape_to(args[i], strlen(args[i]), result + l, L - l);
+ }
+ i++;
+ }
+ result[l] = 0;
+ if (length)
+ *length = l;
+ }
+ return result;
+}
+
+char *escape_args(const char * const *args, size_t *length)
+{
+ return escape_url(NULL, NULL, args, length);
+}
+
+const char **unescape_args(const char *args)
+{
+ const char **r, **q;
+ char c, *p;
+ size_t j, z, l, n, lt;
+
+ lt = n = 0;
+ if (args[0]) {
+ z = 0;
+ do {
+ l = strcspn(&args[z], "&=");
+ j = 1 + unescaped_length(&args[z], l);
+ lt += j;
+ z += l;
+ c = args[z++];
+ if (c == '=') {
+ l = strcspn(&args[z], "&");
+ j = 1 + unescaped_length(&args[z], l);
+ lt += j;
+ z += l;
+ c = args[z++];
+ }
+ n++;
+ } while(c);
+ }
+
+ l = lt + (2 * n + 1) * sizeof(char *);
+ r = malloc(l);
+ if (!r)
+ return r;
+
+ q = r;
+ p = (void*)&r[2 * n + 1];
+ if (args[0]) {
+ z = 0;
+ do {
+ q[0] = p;
+ l = strcspn(&args[z], "&=");
+ j = 1 + unescape_to(&args[z], l, p, lt);
+ lt -= j;
+ p += j;
+ z += l;
+ c = args[z++];
+ if (c != '=')
+ q[1] = NULL;
+ else {
+ q[1] = p;
+ l = strcspn(&args[z], "&");
+ j = 1 + unescape_to(&args[z], l, p, lt);
+ lt -= j;
+ p += j;
+ z += l;
+ c = args[z++];
+ }
+ q = &q[2];
+ } while(c);
+ }
+ q[0] = NULL;
+ return r;
+}
+
+char *escape(const char *text, size_t textlen, size_t *reslength)
+{
+ size_t len;
+ char *result;
+
+ len = 1 + escaped_length(text, textlen);
+ result = malloc(len);
+ if (result)
+ escape_to(text, textlen, result, len);
+ if (reslength)
+ *reslength = len - 1;
+ return result;
+}
+
+char *unescape(const char *text, size_t textlen, size_t *reslength)
+{
+ size_t len;
+ char *result;
+
+ len = 1 + unescaped_length(text, textlen);
+ result = malloc(len);
+ if (result)
+ unescape_to(text, textlen, result, len);
+ if (reslength)
+ *reslength = len - 1;
+ return result;
+}
+
+#if 1
+#include <stdio.h>
+int main(int ac, char **av)
+{
+ int i;
+ char *x = escape_args((void*)++av, NULL);
+ char *y = escape(x, strlen(x), NULL);
+ char *z = unescape(y, strlen(y), NULL);
+ const char **v = unescape_args(x);
+
+ printf("%s\n%s\n%s\n", x, y, z);
+ free(x);
+ free(y);
+ free(z);
+ i = 0;
+ while(v[i]) {
+ printf("%s=%s / %s=%s\n", av[i], av[i+1], v[i], v[i+1]);
+ i += 2;
+ }
+ free(v);
+ return 0;
+}
+#endif
+/* vim: set colorcolumn=80: */
diff --git a/escape.h b/escape.h
new file mode 100644
index 0000000..7d548db
--- /dev/null
+++ b/escape.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 "IoT.bzh"
+ * Author: José Bollo <jose.bollo@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+extern char *escape_url(const char *base, const char *path, const char * const *args, size_t *length);
+extern char *escape_args(const char * const *args, size_t *length);
+extern const char **unescape_args(const char *args);
+
+/* vim: set colorcolumn=80: */