summaryrefslogtreecommitdiff
path: root/src/rexmpp_http_upload.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/rexmpp_http_upload.c')
-rw-r--r--src/rexmpp_http_upload.c205
1 files changed, 205 insertions, 0 deletions
diff --git a/src/rexmpp_http_upload.c b/src/rexmpp_http_upload.c
new file mode 100644
index 0000000..17195fe
--- /dev/null
+++ b/src/rexmpp_http_upload.c
@@ -0,0 +1,205 @@
+/**
+ @file rexmpp_http_upload.c
+ @brief XEP-0363: HTTP file upload
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+*/
+
+#include <syslog.h>
+#include <string.h>
+#include <libgen.h>
+#include <errno.h>
+
+#include "config.h"
+
+#ifdef HAVE_CURL
+#include <curl/curl.h>
+#endif
+
+#include "rexmpp.h"
+#include "rexmpp_http_upload.h"
+
+
+
+#ifdef HAVE_CURL
+void rexmpp_upload_task_finish (struct rexmpp_http_upload_task *task) {
+ task->cb(task->s, task->cb_data, task->get_url);
+ free(task->fname);
+ if (task->fh != NULL) {
+ fclose(task->fh);
+ }
+ if (task->content_type != NULL) {
+ free(task->content_type);
+ }
+ if (task->get_url != NULL) {
+ free(task->get_url);
+ }
+ if (task->http_headers != NULL) {
+ curl_slist_free_all(task->http_headers);
+ }
+ free(task);
+}
+
+void rexmpp_http_upload_slot_cb (rexmpp_t *s,
+ void *ptr,
+ xmlNodePtr request,
+ xmlNodePtr response,
+ int success)
+{
+ (void)request;
+ struct rexmpp_http_upload_task *task = ptr;
+ if (success) {
+ xmlNodePtr slot = rexmpp_xml_find_child(response, "urn:xmpp:http:upload:0", "slot");
+ xmlNodePtr put = rexmpp_xml_find_child(slot, "urn:xmpp:http:upload:0", "put");
+ xmlNodePtr get = rexmpp_xml_find_child(slot, "urn:xmpp:http:upload:0", "get");
+ if (put != NULL && get != NULL) {
+ char *put_url = xmlGetProp(put, "url");
+ char *get_url = xmlGetProp(get, "url");
+ if (put_url != NULL && get_url != NULL) {
+ task->get_url = get_url;
+
+ CURL *ce = curl_easy_init();
+ curl_easy_setopt(ce, CURLOPT_PRIVATE, task);
+ curl_easy_setopt(ce, CURLOPT_UPLOAD, 1);
+ curl_easy_setopt(ce, CURLOPT_READDATA, task->fh);
+ curl_easy_setopt(ce, CURLOPT_URL, put_url);
+
+ xmlNodePtr header = xmlFirstElementChild(put);
+ while (header) {
+ char *header_name = xmlGetProp(header, "name");
+ if (header_name != NULL) {
+ char *header_str = xmlNodeGetContent(header);
+ if (header_str != NULL) {
+ size_t full_header_str_len = strlen(header_name) + 3 + strlen(header_str);
+ char *full_header_str = malloc(full_header_str_len);
+ snprintf(full_header_str, full_header_str_len, "%s: %s",
+ header_name, header_str);
+ task->http_headers =
+ curl_slist_append(task->http_headers, full_header_str);
+ free(full_header_str);
+ free(header_str);
+ }
+ free(header_name);
+ }
+ header = header->next;
+ }
+ curl_easy_setopt(ce, CURLOPT_HTTPHEADER, task->http_headers);
+
+ curl_multi_add_handle(s->curl_multi, ce);
+ rexmpp_log(s, LOG_DEBUG, "Uploading %s to %s", task->fname, put_url);
+ return;
+ } else {
+ rexmpp_log(s, LOG_ERR, "Unexpected structure for a HTTP file upload slot.");
+ if (get_url != NULL) {
+ free(get_url);
+ }
+ }
+ if (put_url != NULL) {
+ free(put_url);
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR, "Unexpected structure for a HTTP file upload slot.");
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR, "Failed to obtain a slot for HTTP file upload.");
+ }
+ rexmpp_upload_task_finish(task);
+}
+
+void rexmpp_http_upload_feature_cb (rexmpp_t *s,
+ void *ptr,
+ xmlNodePtr request,
+ xmlNodePtr response,
+ int success)
+{
+ (void)response;
+ struct rexmpp_http_upload_task *task = ptr;
+ if (! success) {
+ rexmpp_log(s, LOG_ERR, "No HTTP file upload service found.");
+ rexmpp_upload_task_finish(task);
+ return;
+ }
+ xmlNodePtr req = rexmpp_xml_new_node("request", "urn:xmpp:http:upload:0");
+ xmlNewProp(req, "filename", task->fname);
+ char buf[11];
+ snprintf(buf, 11, "%u", task->fsize);
+ xmlNewProp(req, "size", buf);
+ if (task->content_type) {
+ xmlNewProp(req, "content-type", task->content_type);
+ }
+ char *to = xmlGetProp(request, "to");
+ rexmpp_iq_new(s, "get", to, req, rexmpp_http_upload_slot_cb, task);
+ free(to);
+}
+
+rexmpp_err_t
+rexmpp_http_upload (rexmpp_t *s,
+ const char *jid,
+ const char *fname,
+ size_t fsize,
+ FILE *fh,
+ const char *content_type,
+ http_upload_cb cb,
+ void *cb_data)
+{
+ struct rexmpp_http_upload_task *task =
+ malloc(sizeof(struct rexmpp_http_upload_task));
+ task->fname = strdup(fname);
+ task->fsize = fsize;
+ task->fh = fh;
+ task->content_type = content_type ? strdup(content_type) : NULL;
+ task->get_url = NULL;
+ task->http_headers = NULL;
+ task->cb = cb;
+ task->cb_data = cb_data;
+ task->s = s;
+ return rexmpp_disco_find_feature(s, jid, "urn:xmpp:http:upload:0",
+ rexmpp_http_upload_feature_cb, task, 0, 20);
+}
+#else
+rexmpp_err_t
+rexmpp_http_upload (rexmpp_t *s,
+ const char *jid,
+ const char *fname,
+ size_t fsize,
+ FILE *fh,
+ const char *content_type,
+ http_upload_cb cb,
+ void *cb_data)
+{
+ (void)jid;
+ (void)fname;
+ (void)fsize;
+ (void)content_type;
+ rexmpp_log(s, LOG_ERR, "rexmpp is built without curl support");
+ fclose(fh);
+ cb(s, cb_data, NULL);
+ return REXMPP_E_OTHER;
+}
+#endif
+
+rexmpp_err_t
+rexmpp_http_upload_path (rexmpp_t *s,
+ const char *jid,
+ char *fpath,
+ const char *content_type,
+ http_upload_cb cb,
+ void *cb_data)
+{
+ FILE *fh = fopen(fpath, "r");
+ if (fh == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to open %s for reading: %s.\n",
+ fpath, strerror(errno));
+ cb(s, cb_data, NULL);
+ return REXMPP_E_OTHER;
+ }
+
+ char *fname = basename(fpath);
+ fseek(fh, 0, SEEK_END);
+ long fsize = ftell(fh);
+ fseek(fh, 0, SEEK_SET);
+
+ return
+ rexmpp_http_upload(s, jid, fname, fsize, fh, content_type, cb, cb_data);
+}