/* * curllib.c * * Copyright 2016 Michael Rasmussen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include "config.h" #include "curllib.h" #include "caldav.h" #include "xml.h" static gboolean curl_not_initialized = TRUE; static struct Data config; struct MemoryStruct { char * memory; size_t size; }; struct Data { char trace_ascii; /* 1 or 0 */ }; /** * This function is burrowed from the libcurl documentation * @param text * @param stream * @param ptr * @param size * @param nohex */ static void dump(const char* text, FILE* stream, char* ptr, size_t size, char nohex) { size_t i; size_t c; unsigned int width=0x10; if(nohex) /* without the hex output, we can fit more on screen */ width = 0x40; fprintf(stream, "%s, %zd bytes (0x%zx)\n", text, size, size); for(i=0; i=0x20) && (ptr[i+c]<0x80)?ptr[i+c]:'.'); /* check again for 0D0A, to avoid an extra \n if it's at width */ if (nohex && (i+c+2 < size) && ptr[i+c+1]==0x0D && ptr[i+c+2]==0x0A) { i+=(c+3-width); break; } } fputc('\n', stream); /* newline */ } fflush(stream); } /** * This function is burrowed from the libcurl documentation * @param handle * @param type * @param data * @param size * @param userp * @return */ static int my_trace(CURL* handle, curl_infotype type, char* data, size_t size, void* userp) { struct Data* config = (struct Data *)userp; const char* text; (void)handle; /* prevent compiler warning */ switch (type) { case CURLINFO_TEXT: fprintf(stderr, "== Info: %s", data); default: /* in case a new one is introduced to shock us */ return 0; case CURLINFO_HEADER_OUT: text = "=> Send header"; break; case CURLINFO_DATA_OUT: text = "=> Send data"; break; case CURLINFO_SSL_DATA_OUT: text = "=> Send SSL data"; break; case CURLINFO_HEADER_IN: text = "<= Recv header"; break; case CURLINFO_DATA_IN: text = "<= Recv data"; break; case CURLINFO_SSL_DATA_IN: text = "<= Recv SSL data"; break; } dump(text, stderr, data, size, config->trace_ascii); return 0; } /** * This function is burrowed from the libcurl documentation * @param ptr * @param size * @return void* to memory region */ static void* myrealloc(void* ptr, size_t size) { /* There might be a realloc() out there that doesn't like reallocing * NULL pointers, so we take care of it here * */ if(ptr) return realloc(ptr, size); else return malloc(size); } /** * This function is burrowed from the libcurl documentation * @param ptr * @param size * @param nmemb * @param data * @return number of written bytes */ static size_t WriteMemoryCallback(void* ptr, size_t size, size_t nmemb, void* data) { size_t realsize = size * nmemb; struct MemoryStruct* mem = (struct MemoryStruct *)data; mem->memory = (char *)myrealloc(mem->memory, mem->size + realsize + 1); if (mem->memory) { memcpy(&(mem->memory[mem->size]), ptr, realsize); mem->size += realsize; mem->memory[mem->size] = 0; } return realsize; } static struct curl_slist* gslist_to_slist(GSList* list) { struct curl_slist* s = NULL; for (GSList* elem = list; elem; elem = g_slist_next(elem)) { s = curl_slist_append(s, elem->data); } return s; } static void curl_set_request(CURL* curl, RequestType type) { gchar* req = NULL; switch (type) { case DELETE: req = g_strdup("DELETE"); break; case PROPFIND: req = g_strdup("PROPFIND"); break; case PUT: req = g_strdup("PUT"); break; case REPORT: req = g_strdup("REPORT"); break; case GET: req = g_strdup("GET"); break; case OPTIONS: req = g_strdup("OPTIONS"); break; case LOCK: req = g_strdup("LOCK"); break; case UNLOCK: req = g_strdup("UNLOCK"); break; } curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, req); g_free(req); } static CURL* get_curl(Request* request, gboolean debug) { CURL* curl; gchar* userpwd; curl = curl_easy_init(); if (curl) { if (request->password) { userpwd = g_strconcat(request->username, ":", request->password, NULL); curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd); } else { userpwd = g_strconcat(request->username, NULL); curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd); } g_free(userpwd); curl_set_request(curl, request->request); curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); curl_easy_setopt(curl, CURLOPT_USERAGENT, PACKAGE_STRING); curl_easy_setopt(curl, CURLOPT_URL, request->url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, WriteMemoryCallback); if (debug) { config.trace_ascii = 1; curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, my_trace); curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &config); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); } curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, 1); curl_easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL); if (request->request != DELETE && request->request != GET && request->request != OPTIONS) { curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request->data); curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE, (request->data) ? strlen(request->data) : 0); } } return curl; } static GSList* memory_struct_to_gslist(struct MemoryStruct buffer) { GSList* list = NULL; gchar **elems, **e; if (buffer.size > 0) { elems = g_strsplit(buffer.memory, "\n", 0); for (e = elems; *e != NULL; ++e) { if (g_strcmp0(*e, "\r") != 0 || g_strcmp0(*e, "") != 0) list = g_slist_prepend(list, g_strdup(*e)); } g_strfreev(elems); } return list; } Request* request_new() { Request* r = g_new0(Request, 1); return r; } void request_free(Request* request) { if (request) { if (request->headers) { slist_free_gchar(request->headers); request->headers = NULL; } if (request->data) { g_free(request->data); request->data = NULL; } if (request->username) { g_free(request->username); request->username = NULL; } if (request->password) { g_free(request->password); request->password = NULL; } if (request->url) { g_free(request->url); request->url = NULL; } g_free(request); request = NULL; } } Response* request_send(Request* request, gboolean debug) { g_return_val_if_fail(request != NULL, NULL); Response* r = g_new0(Response, 1); CURLcode curlres = 0; struct MemoryStruct data; struct MemoryStruct headers; char error_buf[CURL_ERROR_SIZE]; struct curl_slist* http_headers; data.memory = NULL; data.size = 0; headers.memory = NULL; headers.size = 0; if (curl_not_initialized) { curl_global_init(CURL_GLOBAL_ALL); curl_not_initialized = FALSE; } CURL* curl = get_curl(request, debug); if (curl) { http_headers = gslist_to_slist(request->headers); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, http_headers); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&data); curl_easy_setopt(curl, CURLOPT_WRITEHEADER, (void *)&headers); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, (char *) &error_buf); curlres = curl_easy_perform(curl); if (curlres != CURLE_OK) { request->data = g_strdup(error_buf); } else { curlres = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r->status); r->headers = memory_struct_to_gslist(headers); r->data = g_strndup(data.memory, data.size); if (r->status == 207 && (request->request == DELETE || request->request == PUT)) { /* Error code is returnded inside XML response */ GSList* response = find_element("//d:status", r->data, "d", "DAV:"); if (response) { if (g_strstr_len((gchar *) response->data, -1, "423")) { r->status = 423; } } slist_free_gchar(response); } } if (data.memory) free(data.memory); if (headers.memory) free(headers.memory); curl_slist_free_all(http_headers); curl_easy_cleanup(curl); } return r; } void response_free(Response* response) { if (response) { if (response->headers) { slist_free_gchar(response->headers); response->headers = NULL; } if (response->data) { g_free(response->data); response->data = NULL; } g_free(response); response = NULL; } }