diff options
Diffstat (limited to 'src/tools/http_upload.c')
-rw-r--r-- | src/tools/http_upload.c | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/src/tools/http_upload.c b/src/tools/http_upload.c new file mode 100644 index 00000000..8a1b544d --- /dev/null +++ b/src/tools/http_upload.c @@ -0,0 +1,337 @@ +/* + * http_upload.c + * + * Copyright (C) 2012 - 2016 James Booth <boothj5@gmail.com> + * + * This file is part of Profanity. + * + * Profanity 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 3 of the License, or + * (at your option) any later version. + * + * Profanity 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 Profanity. If not, see <http://www.gnu.org/licenses/>. + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +#define _GNU_SOURCE 1 + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <curl/curl.h> +#include <gio/gio.h> +#include <pthread.h> +#include <assert.h> + +#include "profanity.h" +#include "ui/ui.h" +#include "ui/window.h" +#include "tools/http_upload.h" +#include "event/client_events.h" +#include "config/preferences.h" + +#define FALLBACK_MIMETYPE "application/octet-stream" +#define FALLBACK_MSG "" +#define FILE_HEADER_BYTES 512 + +struct curl_data_t { + char *buffer; + size_t size; +}; + + +static int +_xferinfo(void *userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) +{ + HTTPUpload *upload = (HTTPUpload *)userdata; + + pthread_mutex_lock(&lock); + + if (upload->cancel) { + pthread_mutex_unlock(&lock); + return 1; + } + + if (upload->bytes_sent == ulnow) { + pthread_mutex_unlock(&lock); + return 0; + } else { + upload->bytes_sent = ulnow; + } + + unsigned int ulperc = 0; + if (ultotal != 0) { + ulperc = (100 * ulnow) / ultotal; + } + + char *msg; + if (asprintf(&msg, "Uploading '%s': %d%%", upload->filename, ulperc) == -1) { + msg = FALLBACK_MSG; + } + win_update_entry_message(upload->window, upload->put_url, msg); + free(msg); + + pthread_mutex_unlock(&lock); + + return 0; +} + +#if LIBCURL_VERSION_NUM < 0x072000 +static int +_older_progress(void *p, double dltotal, double dlnow, double ultotal, double ulnow) +{ + return _xferinfo(p, (curl_off_t)dltotal, (curl_off_t)dlnow, (curl_off_t)ultotal, (curl_off_t)ulnow); +} +#endif + +static size_t +_data_callback(void *ptr, size_t size, size_t nmemb, void *data) +{ + size_t realsize = size * nmemb; + struct curl_data_t *mem = (struct curl_data_t *) data; + mem->buffer = realloc(mem->buffer, mem->size + realsize + 1); + + if (mem->buffer) + { + memcpy( &( mem->buffer[ mem->size ] ), ptr, realsize ); + mem->size += realsize; + mem->buffer[ mem->size ] = 0; + } + + return realsize; +} + +void * +http_file_put(void *userdata) +{ + HTTPUpload *upload = (HTTPUpload *)userdata; + + FILE *fd = NULL; + + char *err = NULL; + char *content_type_header; + + CURL *curl; + CURLcode res; + + upload->cancel = 0; + upload->bytes_sent = 0; + + pthread_mutex_lock(&lock); + char* msg; + if (asprintf(&msg, "Uploading '%s': 0%%", upload->filename) == -1) { + msg = FALLBACK_MSG; + } + win_print_with_receipt(upload->window, '!', 0, NULL, 0, THEME_TEXT_ME, NULL, msg, upload->put_url); + free(msg); + + char *cert_path = prefs_get_string(PREF_TLS_CERTPATH); + pthread_mutex_unlock(&lock); + + curl_global_init(CURL_GLOBAL_ALL); + curl = curl_easy_init(); + + curl_easy_setopt(curl, CURLOPT_URL, upload->put_url); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); + + struct curl_slist *headers = NULL; + if (asprintf(&content_type_header, "Content-Type: %s", upload->mime_type) == -1) { + msg = FALLBACK_MSG; + } + headers = curl_slist_append(headers, content_type_header); + headers = curl_slist_append(headers, "Expect:"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + #if LIBCURL_VERSION_NUM >= 0x072000 + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, _xferinfo); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, upload); + #else + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, _older_progress); + curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, upload); + #endif + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + + struct curl_data_t output; + output.buffer = NULL; + output.size = 0; + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _data_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&output); + + curl_easy_setopt(curl, CURLOPT_USERAGENT, "profanity"); + + if (!(fd = fopen(upload->filename, "rb"))) { + if (asprintf(&err, "failed to open '%s'", upload->filename) == -1) { + err = NULL; + } + goto end; + } + + if (cert_path) { + curl_easy_setopt(curl, CURLOPT_CAPATH, cert_path); + } + + curl_easy_setopt(curl, CURLOPT_READDATA, fd); + curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)(upload->filesize)); + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + + if ((res = curl_easy_perform(curl)) != CURLE_OK) { + err = strdup(curl_easy_strerror(res)); + } else { + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + if (output.buffer) { + output.buffer[output.size++] = '\0'; + } + + // XEP-0363 specifies 201 but prosody returns 200 + if (http_code != 200 && http_code != 201) { + if (asprintf(&err, "Server returned %lu", http_code) == -1) { + err = NULL; + } + } + + #if 0 + printf("HTTP Status: %lu\n", http_code); + printf("%s\n", output.buffer); + printf("%lu bytes retrieved\n", (long)output.size); + #endif + } + +end: + curl_easy_cleanup(curl); + curl_global_cleanup(); + curl_slist_free_all(headers); + if (fd) { + fclose(fd); + } + free(content_type_header); + free(output.buffer); + + pthread_mutex_lock(&lock); + prefs_free_string(cert_path); + + if (err) { + char *msg; + if (upload->cancel) { + if (asprintf(&msg, "Uploading '%s' failed: Upload was canceled", upload->filename) == -1) { + msg = FALLBACK_MSG; + } + } else { + if (asprintf(&msg, "Uploading '%s' failed: %s", upload->filename, err) == -1) { + msg = FALLBACK_MSG; + } + win_update_entry_message(upload->window, upload->put_url, msg); + } + cons_show_error(msg); + free(msg); + free(err); + } else { + if (!upload->cancel) { + if (asprintf(&msg, "Uploading '%s': 100%%", upload->filename) == -1) { + msg = FALLBACK_MSG; + } + win_update_entry_message(upload->window, upload->put_url, msg); + //win_update_entry_theme(upload->window, upload->put_url, THEME_THEM); + win_mark_received(upload->window, upload->put_url); + free(msg); + + switch (upload->window->type) { + case WIN_CHAT: + { + ProfChatWin *chatwin = (ProfChatWin*)(upload->window); + assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); + cl_ev_send_msg(chatwin, upload->get_url, upload->get_url); + break; + } + case WIN_PRIVATE: + { + ProfPrivateWin *privatewin = (ProfPrivateWin*)(upload->window); + assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK); + cl_ev_send_priv_msg(privatewin, upload->get_url, upload->get_url); + break; + } + case WIN_MUC: + { + ProfMucWin *mucwin = (ProfMucWin*)(upload->window); + assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK); + cl_ev_send_muc_msg(mucwin, upload->get_url, upload->get_url); + break; + } + default: + break; + } + } + } + + upload_processes = g_slist_remove(upload_processes, upload); + pthread_mutex_unlock(&lock); + + free(upload->filename); + free(upload->mime_type); + free(upload->get_url); + free(upload->put_url); + free(upload); + + return NULL; +} + +char* +file_mime_type(const char* const file_name) +{ + char *out_mime_type; + char file_header[FILE_HEADER_BYTES]; + FILE *fd; + if (!(fd = fopen(file_name, "rb"))) { + return strdup(FALLBACK_MIMETYPE); + } + size_t file_header_size = fread(file_header, 1, FILE_HEADER_BYTES, fd); + fclose(fd); + + char *content_type = g_content_type_guess(file_name, (unsigned char*)file_header, file_header_size, NULL); + if (content_type != NULL) { + char *mime_type = g_content_type_get_mime_type(content_type); + out_mime_type = strdup(mime_type); + g_free(mime_type); + } + else { + return strdup(FALLBACK_MIMETYPE); + } + g_free(content_type); + return out_mime_type; +} + +off_t file_size(const char* const filename) +{ + struct stat st; + stat(filename, &st); + return st.st_size; +} + +int is_regular_file(const char *filename) +{ + struct stat st; + stat(filename, &st); + return S_ISREG(st.st_mode); +} |