diff options
author | William Wennerström <william@wstrm.dev> | 2020-07-05 17:21:20 +0200 |
---|---|---|
committer | William Wennerström <william@wstrm.dev> | 2020-11-16 21:58:09 +0100 |
commit | eebf54c8596abd5c28b405d8860c173207bbec64 (patch) | |
tree | bb1abfaad055aadbb0908dff921259a6e5967aa3 /src | |
parent | 9499df65859abd1404d3f9e96b8af859d3315bff (diff) | |
download | profani-tty-eebf54c8596abd5c28b405d8860c173207bbec64.tar.gz |
Infer filename from content-disposition or URL
The Content-Disposition inferring is probably a bad idea security wise, so I am going to remove it.
Diffstat (limited to 'src')
-rw-r--r-- | src/command/cmd_funcs.c | 243 | ||||
-rw-r--r-- | src/tools/http_download.c | 125 | ||||
-rw-r--r-- | src/tools/http_download.h | 5 |
3 files changed, 254 insertions, 119 deletions
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 7d921ea3..5e8b5cab 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -4,6 +4,7 @@ * * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com> * Copyright (C) 2019 Michael Vetter <jubalh@iodoru.org> + * Copyright (C) 2020 William Wennerström <william@wstrm.dev> * * This file is part of Profanity. * @@ -67,6 +68,7 @@ #include "config/scripts.h" #include "event/client_events.h" #include "tools/http_upload.h" +#include "tools/http_download.h" #include "tools/autocomplete.h" #include "tools/parser.h" #include "tools/bookmark_ignore.h" @@ -4805,6 +4807,43 @@ cmd_disco(ProfWin* window, const char* const command, gchar** args) return TRUE; } + +char *_add_omemo_stream(int *fd, FILE **fh, char **err) { + // Create temporary file for writing ciphertext. + int tmpfd; + char *tmpname = NULL; + if ((tmpfd = g_file_open_tmp("profanity.XXXXXX", &tmpname, NULL)) == -1) { + *err = "Unable to create temporary file for encrypted transfer."; + return NULL; + } + FILE *tmpfh = fdopen(tmpfd, "wb"); + + // The temporary ciphertext file should be removed after it has + // been closed. + remove(tmpname); + free(tmpname); + + int crypt_res; + char *fragment; + fragment = omemo_encrypt_file(*fh, tmpfh, file_size(*fd), &crypt_res); + if (crypt_res != 0) { + fclose(tmpfh); + return NULL; + } + + // Force flush as the upload will read from the same stream. + fflush(tmpfh); + rewind(tmpfh); + + fclose(*fh); // Also closes descriptor. + + // Switch original stream with temporary ciphertext stream. + *fd = tmpfd; + *fh = tmpfh; + + return fragment; +} + gboolean cmd_sendfile(ProfWin* window, const char* const command, gchar** args) { @@ -4855,59 +4894,29 @@ cmd_sendfile(ProfWin* window, const char* const command, gchar** args) case WIN_CHAT: { ProfChatWin *chatwin = (ProfChatWin*)window; - assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); #ifdef HAVE_OMEMO if (chatwin->is_omemo) { - - // Create temporary file for writing ciphertext. - int tmpfd; - char *tmpname = NULL; - if ((tmpfd = g_file_open_tmp("profanity.XXXXXX", &tmpname, NULL)) == -1) { - char *msg = "Unable to create temporary file for encrypted transfer."; - cons_show_error(msg); - win_println(window, THEME_ERROR, "-", msg); - fclose(fh); - goto out; - } - FILE *tmpfh = fdopen(tmpfd, "wb"); - - // The temporary ciphertext file should be removed after it has - // been closed. - remove(tmpname); - free(tmpname); - - int crypt_res; + char *err = NULL; alt_scheme = OMEMO_AESGCM_URL_SCHEME; - alt_fragment = omemo_encrypt_file(fh, tmpfh, file_size(fd), &crypt_res); - if (crypt_res != 0) { - char *msg = "Failed to encrypt file."; - cons_show_error(msg); - win_println(window, THEME_ERROR, "-", msg); - fclose(fh); - fclose(tmpfh); + alt_fragment = _add_omemo_stream(&fd, &fh, &err); + if (err != NULL) { + cons_show_error(err); + win_println(window, THEME_ERROR, "-", err); goto out; } - - // Force flush as the upload will read from the same stream. - fflush(tmpfh); - rewind(tmpfh); - - fclose(fh); // Also closes descriptor. - - // Switch original stream with temporary ciphertext stream. - fd = tmpfd; - fh = tmpfh; - break; } #endif - if ((chatwin->pgp_send && !prefs_get_boolean(PREF_PGP_SENDFILE)) - || (chatwin->is_otr && !prefs_get_boolean(PREF_OTR_SENDFILE))) { - cons_show_error("Uploading unencrypted files disabled. See /otr sendfile or /pgp sendfile."); - win_println(window, THEME_ERROR, "-", "Sending encrypted files via http_upload is not possible yet."); - goto out; + if (window->type == WIN_CHAT) { + assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); + if ((chatwin->pgp_send && !prefs_get_boolean(PREF_PGP_SENDFILE)) + || (chatwin->is_otr && !prefs_get_boolean(PREF_OTR_SENDFILE))) { + cons_show_error("Uploading unencrypted files disabled. See /otr sendfile or /pgp sendfile."); + win_println(window, THEME_ERROR, "-", "Sending encrypted files via http_upload is not possible yet."); + goto out; + } } break; } @@ -9154,91 +9163,57 @@ cmd_url_open(ProfWin* window, const char* const command, gchar** args) return TRUE; } -gboolean -cmd_url_save(ProfWin* window, const char* const command, gchar** args) -{ - if (window->type != WIN_CHAT && window->type != WIN_MUC && window->type != WIN_PRIVATE) { - cons_show("url save not supported in this window"); - return TRUE; - } - if (args[1] == NULL) { - cons_bad_cmd_usage(command); - return TRUE; - } - - gchar* uri = args[1]; - gchar* target_path = g_strdup(args[2]); +void _url_save_fallback_method(ProfWin *window, const char *url, const char *directory, const char *filename) { + HTTPDownload *download = malloc(sizeof(HTTPDownload)); + download->window = window; + download->url = strdup(url); - GFile* file = g_file_new_for_uri(uri); - - gchar* target_dir = NULL; - gchar* base_name = NULL; - - if (target_path == NULL) { - target_dir = g_strdup("./"); - base_name = g_file_get_basename(file); - if (0 == g_strcmp0(base_name, ".")) { - g_free(base_name); - base_name = g_strdup("saved_url_content.html"); - } - target_path = g_strconcat(target_dir, base_name, NULL); + if (filename) { + download->filename = strdup(filename); + } else { + download->filename = NULL; } - if (g_file_test(target_path, G_FILE_TEST_EXISTS) && g_file_test(target_path, G_FILE_TEST_IS_DIR)) { - target_dir = g_strdup(target_path); - base_name = g_file_get_basename(file); - g_free(target_path); - target_path = g_strconcat(target_dir, "/", base_name, NULL); + if (directory) { + download->directory = strdup(directory); + } else { + download->directory = NULL; } - g_object_unref(file); - file = NULL; + pthread_create(&(download->worker), NULL, &http_file_get, download); + http_download_add_download(download); +} - if (base_name == NULL) { - base_name = g_path_get_basename(target_path); - target_dir = g_path_get_dirname(target_path); +void _url_save_external_method(const char *scheme_cmd, const char *url, const char *directory, char *filename) { + if (!filename) { + filename = http_filename_from_url(url); } - if (!g_file_test(target_dir, G_FILE_TEST_EXISTS) || !g_file_test(target_dir, G_FILE_TEST_IS_DIR)) { - cons_show("%s does not exist or is not a directory.", target_dir); - g_free(target_path); - g_free(target_dir); - g_free(base_name); - return TRUE; + // Explicitly use "." as directory if no directory has been passed. + char *fp = NULL; + if (directory == NULL) { + fp = g_build_filename(".", filename, NULL); + } else { + fp = g_build_filename(directory, filename, NULL); } - gchar* scheme = g_uri_parse_scheme(uri); - if (scheme == NULL) { - cons_show("URL '%s' is not valid.", uri); - g_free(target_path); - g_free(target_dir); - g_free(base_name); - return TRUE; + if (!g_file_test(directory, G_FILE_TEST_EXISTS) || + !g_file_test(directory, G_FILE_TEST_IS_DIR)) { + cons_show_error("Directory '%s' does not exist or is not a directory.", directory); + return; } - gchar* scheme_cmd = NULL; - - if (0 == g_strcmp0(scheme, "http") - || 0 == g_strcmp0(scheme, "https") - || 0 == g_strcmp0(scheme, OMEMO_AESGCM_URL_SCHEME) - ) { - scheme_cmd = prefs_get_string_with_option(PREF_URL_SAVE_CMD, scheme); - } - - g_free(scheme); - - gchar** argv = g_strsplit(scheme_cmd, " ", 0); - g_free(scheme_cmd); + gchar **argv = g_strsplit(scheme_cmd, " ", 0); guint num_args = 0; while (argv[num_args]) { if (0 == g_strcmp0(argv[num_args], "%u")) { g_free(argv[num_args]); - argv[num_args] = g_strdup(uri); + argv[num_args] = g_strdup(url); } else if (0 == g_strcmp0(argv[num_args], "%p")) { g_free(argv[num_args]); - argv[num_args] = target_path; + argv[num_args] = fp; } num_args++; } @@ -9246,12 +9221,56 @@ cmd_url_save(ProfWin* window, const char* const command, gchar** args) if (!call_external(argv, NULL, NULL)) { cons_show_error("Unable to save url: check the logs for more information."); } else { - cons_show("URL '%s' has been saved into '%s'.", uri, target_path); + cons_show("URL '%s' has been saved to '%s'.", url, fp); } +} - g_free(target_dir); - g_free(base_name); - g_strfreev(argv); +gboolean +cmd_url_save(ProfWin *window, const char *const command, gchar **args) +{ + if (window->type != WIN_CHAT && + window->type != WIN_MUC && + window->type != WIN_PRIVATE) { + cons_show_error("`/url save` is not supported in this window."); + return TRUE; + } + + if (args[1] == NULL) { + cons_bad_cmd_usage(command); + return TRUE; + } + + gchar *url = args[1]; + gchar *path = g_strdup(args[2]); + + gchar *scheme = g_uri_parse_scheme(url); + if (scheme == NULL) { + cons_show("URL '%s' is not valid.", url); + g_free(url); + return TRUE; + } + + gchar *directory = NULL; + gchar *filename = NULL; + if (path != NULL) { + directory = g_path_get_dirname(path); + filename = g_path_get_basename(path); + } + + gchar *scheme_cmd = prefs_get_string_with_option(PREF_URL_SAVE_CMD, scheme); + if (scheme_cmd == NULL) { + if (g_strcmp0(scheme, "http") == 0 + || g_strcmp0(scheme, "https") == 0 + || g_strcmp0(scheme, OMEMO_AESGCM_URL_SCHEME) == 0) { + _url_save_fallback_method(window, url, directory, filename); + } else { + cons_show_error("No download method defined for the scheme '%s'.", scheme); + } + } else { + _url_save_external_method(scheme_cmd, url, directory, filename); + } + + g_free(scheme_cmd); return TRUE; } diff --git a/src/tools/http_download.c b/src/tools/http_download.c index 80916385..5a0f6f18 100644 --- a/src/tools/http_download.c +++ b/src/tools/http_download.c @@ -104,13 +104,119 @@ _older_progress(void *p, double dltotal, double dlnow, double ultotal, double ul } #endif +char *http_filename_from_header(char *header) { + const char *header_tag_cd = "Content-Disposition:"; + const int header_tag_cd_len = strlen(header_tag_cd); + + if (!header) { + return NULL; // Bad header. + } + + if (strncasecmp(header, header_tag_cd, header_tag_cd_len) == 0) { + header += header_tag_cd_len; // Move to header content. + } else { + return NULL; // Not a CD header. + } + + const char *filename_key = "filename="; + const size_t filename_key_len = strlen(filename_key); + + char *value = strcasestr(header, filename_key); + if (!value) { + return NULL; // No filename key found. + } + + value += filename_key_len; // Move to key value. + + char fn[4096]; + char *pf = fn; + while(*value != '\0' && *value != ';') { + *pf++ = *value++; + } + *pf = '\0'; + + if (!strlen(fn)) { + return NULL; // Empty tag. + } + + return strdup(fn); +} + +char *http_filename_from_url(const char *url) { + const char *default_name = "index.html"; + + GFile *file = g_file_new_for_uri(url); + char *filename = g_file_get_basename(file); + g_object_unref(file); + + if (g_strcmp0(filename, ".") == 0 + || g_strcmp0(filename, G_DIR_SEPARATOR_S) == 0) { + g_free(filename); + return strdup(default_name); + } + + return filename; +} + +static size_t _header_callback(char *data, size_t size, size_t nitems, void *userdata) { + char *header = (char*)data; + + HTTPDownload *download = (HTTPDownload *)userdata; + size *= nitems; + + if (download->filename != NULL) { + return size; // No-op. + } + + download->filename = http_filename_from_header(header); + + return size; +} + +FILE *_get_filehandle(const char *directory, const char *filename) { + gchar *fp; + FILE *fh; + + // Explicitly use "." as directory if no directory has been passed. + if (directory == NULL) { + fp = g_build_filename(".", filename, NULL); + } else { + fp = g_build_filename(directory, filename, NULL); + } + + fh = fopen(fp, "wb"); + g_free(fp); + return fh; +} + +static size_t _write_callback(void *buffer, size_t size, size_t nmemb, void *userdata) { + HTTPDownload *download = (HTTPDownload *)userdata; + size *= nmemb; + + if (download->filename == NULL) { + download->filename = http_filename_from_url(download->url); + } + + if (download->filename == NULL || download->directory == NULL) { + return 0; // Missing file name or directory, write no data. + } + + if (download->filehandle == NULL ) { + FILE *fh = _get_filehandle(download->directory, download->filename); + if (!fh) { + return 0; // Unable to open file handle. + } + download->filehandle = fh; + } + + return fwrite(buffer, size, nmemb, userdata); +} + void * http_file_get(void *userdata) { HTTPDownload *download = (HTTPDownload *)userdata; - FILE *fh = NULL; - char *err = NULL; CURL *curl; @@ -144,9 +250,12 @@ http_file_get(void *userdata) #endif curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); - fh = download->filehandle; + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, _header_callback); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)download); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)download); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&fh); curl_easy_setopt(curl, CURLOPT_USERAGENT, "profanity"); if (cert_path) { @@ -160,8 +269,8 @@ http_file_get(void *userdata) curl_easy_cleanup(curl); curl_global_cleanup(); - if (fh) { - fclose(fh); + if (download->filehandle) { + fclose(download->filehandle); } pthread_mutex_lock(&lock); @@ -188,7 +297,7 @@ http_file_get(void *userdata) msg = strdup(FALLBACK_MSG); } win_update_entry_message(download->window, download->url, msg); - win_mark_received(download->window, download->put_url); + win_mark_received(download->window, download->url); free(msg); } } @@ -197,6 +306,8 @@ http_file_get(void *userdata) pthread_mutex_unlock(&lock); free(download->url); + free(download->filename); + free(download->directory); free(download); return NULL; diff --git a/src/tools/http_download.h b/src/tools/http_download.h index 7348b77c..b0377d93 100644 --- a/src/tools/http_download.h +++ b/src/tools/http_download.h @@ -48,6 +48,8 @@ typedef struct http_download_t { char *url; + char *filename; + char *directory; FILE *filehandle; curl_off_t bytes_received; ProfWin *window; @@ -60,4 +62,7 @@ void* http_file_get(void *userdata); void http_download_cancel_processes(ProfWin *window); void http_download_add_download(HTTPDownload *download); +char *http_filename_from_url(const char *url); +char *http_filename_from_header(char *header); + #endif |