Merge pull request #2131 from profanity-im/fix/dl

Improve (encrypted) file downloads
This commit is contained in:
Michael Vetter
2026-03-26 10:21:17 +01:00
committed by GitHub
10 changed files with 127 additions and 56 deletions

View File

@@ -9209,18 +9209,30 @@ _prepare_filename(ProfWin* window, gchar* url, gchar* path)
break;
}
// Ensure that the downloads directory exists for saving cleartexts.
auto_gchar gchar* downloads_dir = path ? get_expanded_path(path) : files_get_download_path(jid);
if (g_mkdir_with_parents(downloads_dir, S_IRWXU) != 0) {
cons_show_error("Failed to create download directory "
"at '%s' with error '%s'",
downloads_dir, strerror(errno));
return NULL;
auto_gchar gchar* expanded_path = path ? get_expanded_path(path) : files_get_download_path(jid);
// If path is provided and doesn't look like a directory, create its parent.
// Otherwise, create the directory itself.
if (path && !g_str_has_suffix(expanded_path, G_DIR_SEPARATOR_S) && !g_file_test(expanded_path, G_FILE_TEST_IS_DIR)) {
auto_gchar gchar* dirname = g_path_get_dirname(expanded_path);
if (g_mkdir_with_parents(dirname, S_IRWXU) != 0) {
cons_show_error("Failed to create download directory "
"at '%s' with error '%s'",
dirname, strerror(errno));
return NULL;
}
} else {
if (g_mkdir_with_parents(expanded_path, S_IRWXU) != 0) {
cons_show_error("Failed to create download directory "
"at '%s' with error '%s'",
expanded_path, strerror(errno));
return NULL;
}
}
// Generate an unique filename from the URL that should be stored in the
// downloads directory.
return unique_filename_from_url(url, downloads_dir);
return unique_filename_from_url(url, expanded_path);
}
#ifdef HAVE_OMEMO

View File

@@ -43,7 +43,7 @@ aesgcm_file_get(void* userdata)
// and tag stored in the URL fragment.
if (omemo_parse_aesgcm_url(aesgcm_dl->url, &https_url, &fragment) != 0) {
cons_show_error("Download failed: Cannot parse URL '%s'.", aesgcm_dl->url);
http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id,
http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id, THEME_ERROR, ENTRY_ERROR,
"Download failed: Cannot parse URL '%s'.",
aesgcm_dl->url);
return NULL;
@@ -54,7 +54,7 @@ aesgcm_file_get(void* userdata)
auto_gchar gchar* tmpname = NULL;
auto_gfd gint tmpfd = 0;
if ((tmpfd = g_file_open_tmp("profanity.XXXXXX", &tmpname, NULL)) == -1) {
http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id,
http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id, THEME_ERROR, ENTRY_ERROR,
"Downloading '%s' failed: Unable to create "
"temporary ciphertext file for writing "
"(%s).",
@@ -65,7 +65,7 @@ aesgcm_file_get(void* userdata)
// Open the target file for storing the cleartext.
auto_FILE FILE* outfh = fopen(aesgcm_dl->filename, "wb");
if (outfh == NULL) {
http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id,
http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id, THEME_ERROR, ENTRY_ERROR,
"Downloading '%s' failed: Unable to open "
"output file at '%s' for writing (%s).",
https_url, aesgcm_dl->filename,
@@ -80,9 +80,11 @@ aesgcm_file_get(void* userdata)
http_dl->worker = aesgcm_dl->worker;
http_dl->id = strdup(aesgcm_dl->id);
http_dl->url = strdup(https_url);
http_dl->display_url = strdup(aesgcm_dl->url);
http_dl->filename = strdup(tmpname);
http_dl->cmd_template = NULL;
http_dl->silent = FALSE;
http_dl->silent_done = TRUE;
http_dl->return_bytes_received = TRUE;
aesgcm_dl->http_dl = http_dl;
@@ -95,7 +97,7 @@ aesgcm_file_get(void* userdata)
FILE* tmpfh = fopen(tmpname, "rb");
if (tmpfh == NULL) {
http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id,
http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id, THEME_ERROR, ENTRY_ERROR,
"Downloading '%s' failed: Unable to open "
"temporary file at '%s' for reading (%s).",
aesgcm_dl->url, tmpname,
@@ -110,13 +112,15 @@ aesgcm_file_get(void* userdata)
remove(tmpname);
if (crypt_res != GPG_ERR_NO_ERROR) {
http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id,
http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id, THEME_ERROR, ENTRY_ERROR,
"Downloading '%s' failed: Failed to decrypt "
"file (%s).",
https_url, gcry_strerror(crypt_res));
aesgcm_dl->url, gcry_strerror(crypt_res));
} else {
http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id,
"Decrypted file saved to '%s'", aesgcm_dl->filename);
http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id, THEME_ONLINE, ENTRY_COMPLETED,
"Downloading '%s': done\nSaved to '%s'",
aesgcm_dl->url, aesgcm_dl->filename);
win_mark_received(aesgcm_dl->window, aesgcm_dl->id);
}
if (aesgcm_dl->cmd_template != NULL) {
@@ -126,7 +130,7 @@ aesgcm_file_get(void* userdata)
// TODO: Log the error.
if (!call_external(argv)) {
http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id,
http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id, THEME_ERROR, ENTRY_ERROR,
"Downloading '%s' failed: Unable to call "
"command '%s' with file at '%s' (%s).",
aesgcm_dl->url,

View File

@@ -19,7 +19,7 @@
#define FALLBACK_MSG ""
void
http_print_transfer_update(ProfWin* window, char* id, const char* fmt, ...)
http_print_transfer_update(ProfWin* window, char* id, theme_item_t theme_item, int flags, const char* fmt, ...)
{
va_list args;
@@ -29,7 +29,7 @@ http_print_transfer_update(ProfWin* window, char* id, const char* fmt, ...)
va_end(args);
if (window->type != WIN_CONSOLE) {
win_update_entry_message(window, id, msg->str);
win_update_entry(window, id, msg->str, theme_item, flags);
} else {
cons_show("%s", msg->str);
}
@@ -38,7 +38,7 @@ http_print_transfer_update(ProfWin* window, char* id, const char* fmt, ...)
}
void
http_print_transfer(ProfWin* window, char* id, const char* fmt, ...)
http_print_transfer(ProfWin* window, char* id, theme_item_t theme_item, const char* fmt, ...)
{
va_list args;
@@ -47,7 +47,7 @@ http_print_transfer(ProfWin* window, char* id, const char* fmt, ...)
g_string_vprintf(msg, fmt, args);
va_end(args);
win_print_http_transfer(window, msg->str, id);
win_print_status_with_id(window, msg->str, id, theme_item, 0);
g_string_free(msg, TRUE);
}

View File

@@ -12,7 +12,7 @@
#include "ui/window.h"
void http_print_transfer(ProfWin* window, char* id, const char* fmt, ...);
void http_print_transfer_update(ProfWin* window, char* id, const char* fmt, ...);
void http_print_transfer(ProfWin* window, char* id, theme_item_t theme_item, const char* fmt, ...);
void http_print_transfer_update(ProfWin* window, char* id, theme_item_t theme_item, int flags, const char* fmt, ...);
#endif

View File

@@ -32,7 +32,6 @@
#include "common.h"
GSList* download_processes = NULL;
gboolean silent = FALSE;
static int
_xferinfo(void* userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
@@ -58,9 +57,18 @@ _xferinfo(void* userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultot
dlperc = (100 * dlnow) / dltotal;
}
if (!silent)
http_print_transfer_update(download->window, download->url,
"Downloading '%s': %d%%", download->url, dlperc);
if (!download->silent) {
const char* url = download->display_url ? download->display_url : download->url;
if (dlnow == dltotal && dltotal > 0) {
if (!download->silent_done) {
http_print_transfer_update(download->window, download->id, THEME_ONLINE, ENTRY_COMPLETED,
"Downloading '%s': done", url);
}
} else {
http_print_transfer_update(download->window, download->id, THEME_DEFAULT, 0,
"Downloading '%s': %d%%", url, dlperc);
}
}
pthread_mutex_unlock(&lock);
@@ -85,20 +93,20 @@ http_file_get(void* userdata)
CURL* curl;
CURLcode res;
silent = download->silent;
download->cancel = 0;
download->bytes_received = 0;
pthread_mutex_lock(&lock);
if (!silent) {
http_print_transfer(download->window, download->id,
"Downloading '%s': 0%%", download->url);
const char* display_url = download->display_url ? download->display_url : download->url;
if (!download->silent) {
http_print_transfer(download->window, download->id, THEME_DEFAULT,
"Downloading '%s': 0%%", display_url);
}
FILE* outfh = fopen(download->filename, "wb");
if (outfh == NULL) {
http_print_transfer_update(download->window, download->id,
http_print_transfer_update(download->window, download->id, THEME_ERROR, ENTRY_ERROR,
"Downloading '%s' failed: Unable to open "
"output file at '%s' for writing (%s).",
download->url, download->filename,
@@ -169,22 +177,24 @@ http_file_get(void* userdata)
g_free(cert_path);
if (err) {
if (download->cancel) {
http_print_transfer_update(download->window, download->id,
http_print_transfer_update(download->window, download->id, THEME_ERROR, ENTRY_ERROR,
"Downloading '%s' failed: "
"Download was canceled",
download->url);
display_url);
} else {
http_print_transfer_update(download->window, download->id,
http_print_transfer_update(download->window, download->id, THEME_ERROR, ENTRY_ERROR,
"Downloading '%s' failed: %s",
download->url, err);
display_url, err);
}
free(err);
} else {
if (!download->cancel && !silent) {
http_print_transfer_update(download->window, download->id,
"Downloading '%s': done\nSaved to '%s'",
download->url, download->filename);
win_mark_received(download->window, download->id);
if (!download->cancel) {
if (!download->silent && !download->silent_done) {
http_print_transfer_update(download->window, download->id, THEME_ONLINE, ENTRY_COMPLETED,
"Downloading '%s': done\nSaved to '%s'",
display_url, download->filename);
win_mark_received(download->window, download->id);
}
if (download->return_bytes_received) {
ret = malloc(sizeof(*ret));
if (ret) {
@@ -201,10 +211,10 @@ http_file_get(void* userdata)
// TODO: Log the error.
if (!call_external(argv)) {
http_print_transfer_update(download->window, download->id,
http_print_transfer_update(download->window, download->id, THEME_ERROR, ENTRY_ERROR,
"Downloading '%s' failed: Unable to call "
"command '%s' with file at '%s' (%s).",
download->url,
display_url,
download->cmd_template,
download->filename,
"TODO: Log the error");
@@ -221,6 +231,7 @@ out:
free(download->filename);
free(download->url);
free(download->display_url);
free(download->id);
free(download);

View File

@@ -25,6 +25,7 @@ typedef struct http_download_t
{
char* id;
char* url;
char* display_url;
char* filename;
char* cmd_template;
curl_off_t bytes_received;
@@ -32,6 +33,7 @@ typedef struct http_download_t
pthread_t worker;
int cancel;
gboolean silent;
gboolean silent_done;
gboolean return_bytes_received;
} HTTPDownload;

View File

@@ -65,11 +65,21 @@ _xferinfo(void* userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultot
ulperc = (100 * ulnow) / ultotal;
}
gchar* msg = g_strdup_printf("Uploading '%s': %d%%", upload->filename, ulperc);
gchar* msg = NULL;
theme_item_t theme = THEME_DEFAULT;
int flags = 0;
if (ulnow == ultotal && ultotal > 0) {
msg = g_strdup_printf("Uploading '%s': done", upload->filename);
theme = THEME_ONLINE;
flags = ENTRY_COMPLETED;
} else {
msg = g_strdup_printf("Uploading '%s': %d%%", upload->filename, ulperc);
}
if (!msg) {
msg = g_strdup(FALLBACK_MSG);
}
win_update_entry_message(upload->window, upload->put_url, msg);
win_update_entry(upload->window, upload->put_url, msg, theme, flags);
g_free(msg);
pthread_mutex_unlock(&lock);
@@ -150,7 +160,7 @@ http_file_put(void* userdata)
if (!msg) {
msg = g_strdup(FALLBACK_MSG);
}
win_print_http_transfer(upload->window, msg, upload->put_url);
win_print_status_with_id(upload->window, msg, upload->put_url, THEME_DEFAULT, 0);
auto_gchar gchar* cert_path = prefs_get_string(PREF_TLS_CERTPATH);
auto_gchar gchar* cafile = cafile_get_name();
@@ -278,21 +288,22 @@ http_file_put(void* userdata)
if (!err_msg) {
err_msg = g_strdup(FALLBACK_MSG);
}
win_update_entry(upload->window, upload->put_url, err_msg, THEME_ERROR, ENTRY_ERROR);
} else {
err_msg = g_strdup_printf("Uploading '%s' failed: %s", upload->filename, err);
if (!err_msg) {
err_msg = g_strdup(FALLBACK_MSG);
}
win_update_entry_message(upload->window, upload->put_url, err_msg);
win_update_entry(upload->window, upload->put_url, err_msg, THEME_ERROR, ENTRY_ERROR);
}
cons_show_error(err_msg);
} else {
if (!upload->cancel) {
auto_gchar gchar* status_msg = g_strdup_printf("Uploading '%s': 100%%", upload->filename);
auto_gchar gchar* status_msg = g_strdup_printf("Uploading '%s': done", upload->filename);
if (!status_msg) {
status_msg = g_strdup(FALLBACK_MSG);
}
win_update_entry_message(upload->window, upload->put_url, status_msg);
win_update_entry(upload->window, upload->put_url, status_msg, THEME_ONLINE, ENTRY_COMPLETED);
win_mark_received(upload->window, upload->put_url);
char* url = NULL;

View File

@@ -25,12 +25,14 @@
#include "otr/otr.h"
#endif
#define NO_ME 1
#define NO_DATE 2
#define NO_EOL 4
#define NO_COLOUR_FROM 8
#define NO_COLOUR_DATE 16
#define UNTRUSTED 32
#define NO_ME 1
#define NO_DATE 2
#define NO_EOL 4
#define NO_COLOUR_FROM 8
#define NO_COLOUR_DATE 16
#define UNTRUSTED 32
#define ENTRY_COMPLETED 64
#define ENTRY_ERROR 128
// core UI
void ui_init(void);

View File

@@ -1659,7 +1659,7 @@ win_appendln_highlight(ProfWin* window, theme_item_t theme_item, const char* con
void
win_print_http_transfer(ProfWin* window, const char* const message, char* id)
{
win_print_outgoing_with_receipt(window, "!", NULL, message, id, NULL);
win_print_status_with_id(window, message, id, THEME_DEFAULT, 0);
}
void
@@ -1708,6 +1708,33 @@ win_update_entry_message(ProfWin* window, const char* const id, const char* cons
}
}
void
win_update_entry(ProfWin* window, const char* const id, const char* const message, theme_item_t theme_item, int flags)
{
if (window->type == WIN_CONSOLE)
return;
ProfBuffEntry* entry = buffer_get_entry_by_id(window->layout->buffer, id);
if (entry) {
if (message) {
free(entry->message);
entry->message = strdup(message);
}
entry->theme_item = theme_item;
entry->flags |= flags;
win_redraw(window);
}
}
void
win_print_status_with_id(ProfWin* window, const char* const message, char* id, theme_item_t theme_item, int flags)
{
GDateTime* time = g_date_time_new_now_local();
int y_start_pos = getcury(window->layout->win);
_win_print_internal(window, "!", 0, time, flags, theme_item, NULL, message, NULL);
buffer_append(window->layout->buffer, "!", 0, time, flags, theme_item, NULL, NULL, message, NULL, id, y_start_pos, getcury(window->layout->win));
g_date_time_unref(time);
}
void
win_remove_entry_message(ProfWin* window, const char* const id)
{

View File

@@ -56,6 +56,8 @@ void win_sub_print(WINDOW* win, char* msg, gboolean newline, gboolean wrap, int
void win_sub_newline_lazy(WINDOW* win);
void win_mark_received(ProfWin* window, const char* const id);
void win_update_entry_message(ProfWin* window, const char* const id, const char* const message);
void win_update_entry(ProfWin* window, const char* const id, const char* const message, theme_item_t theme_item, int flags);
void win_print_status_with_id(ProfWin* window, const char* const message, char* id, theme_item_t theme_item, int flags);
gboolean win_has_active_subwin(ProfWin* window);