XEP-0363: HTTP File Upload

Add initial support for XEP-0363. HTTP uploads can be used when colon
separated values for stdin is enabled:

~~~

u::::<filename>|<size (bytes)>[|<content-type>]

u::::example.png%7C16698

u::::tr.png%7C16698%7Cimage%2Fpng
~~~

The reponse:

~~~
U:upload.example.com:user@example.com/477937350262208314215778:https%3A%2F%2Fexample.com%2Fupload%2F1234%2Fabc%2Fexample.png%7Chttps%3A%2F%2Fexample.com%2Fupload%2F1234%2Fabc%2Fexample.png
~~~

TODO:

* support PUT header elements
* support/test error conditions

Questions:

* save the maximum file size returned by the server and disallow uploads
  larger than the value?

* xmppipe is "pinned" to the upload server returned in the IQ reply (the
  "to" field is ignored)

  * allow other upload servers?
  * error if different upload server is specified in "u:<from>:<to>"?
master
Michael Santos 5 years ago
parent 11bc581608
commit 31d6a03bfc

@ -0,0 +1,91 @@
/* Copyright (c) 2019, Michael Santos <michael.santos@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "xmppipe.h"
int handle_iq(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza,
void *const userdata) {
xmppipe_state_t *state = userdata;
xmpp_stanza_t *slot;
xmpp_stanza_t *item;
const char *from = NULL;
const char *to = NULL;
const char *get = NULL;
const char *put = NULL;
char *efrom;
char *eto;
char *eget;
char *eput;
from = xmpp_stanza_get_attribute(stanza, "from");
if (from == NULL)
return 1;
slot = xmpp_stanza_get_child_by_ns(stanza, "urn:xmpp:http:upload:0");
/* only handles XEP 0363 */
if (slot == NULL)
return 1;
to = xmpp_stanza_get_attribute(stanza, "to");
if (to == NULL)
return 1;
if (XMPPIPE_STRNEQ(from, state->upload)) {
if (state->verbose)
(void)fprintf(stderr,
"error: received XEP363 slot from: %s (using: %s)\n", from,
state->upload);
return 1;
}
for (item = xmpp_stanza_get_children(slot); item != NULL;
item = xmpp_stanza_get_next(item)) {
const char *name = xmpp_stanza_get_name(item);
if (name == NULL)
continue;
if (XMPPIPE_STREQ(name, "get"))
get = xmpp_stanza_get_attribute(item, "url");
if (XMPPIPE_STREQ(name, "put"))
put = xmpp_stanza_get_attribute(item, "url");
}
if (get == NULL || put == NULL)
return 1;
efrom = xmppipe_fmt_encode(from);
eto = xmppipe_fmt_encode(to);
eget = xmppipe_fmt_encode(get);
eput = xmppipe_fmt_encode(put);
(void)printf("U:%s:%s:%s:%s%%7c%s\n",
state->opt & XMPPIPE_OPT_GROUPCHAT ? "groupchat" : "chat", efrom,
eto, eget, eput);
free(efrom);
free(eto);
free(eget);
free(eput);
return 1;
}

@ -327,6 +327,7 @@ int xmppipe_stream_init(xmppipe_state_t *state) {
xmpp_handler_add(state->conn, handle_message, NULL, "message", NULL, state);
xmpp_handler_add(state->conn, handle_version, "jabber:iq:version", "iq", NULL,
state);
xmpp_handler_add(state->conn, handle_iq, NULL, "iq", "result", state);
xmpp_id_handler_add(state->conn, handle_ping_reply, "c2s1", state);
/* XXX multiple handlers can be called for each event
@ -521,6 +522,11 @@ int handle_disco_info(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza,
if (var == NULL)
continue;
if (XMPPIPE_STREQ(var, "urn:xmpp:http:upload:0")) {
state->upload = xmppipe_strdup(from);
continue;
}
if (XMPPIPE_STRNEQ(var, "http://jabber.org/protocol/muc"))
continue;
@ -533,7 +539,7 @@ int handle_disco_info(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza,
xmppipe_muc_unlock(state);
}
return 0;
return 1;
}
return 1;
@ -563,7 +569,8 @@ static void usage(xmppipe_state_t *state) {
" -S, --subject <subject> set MUC subject\n"
" -a, --address <addr:port> set XMPP server address (port is "
"optional)\n"
" -F, --format <text|csv> stdin is text (default) or colon separated values\n"
" -F, --format <text|csv> stdin is text (default) or colon "
"separated values\n"
" -d, --discard discard stdin when MUC is empty\n"
" -D, --discard-to-stdout discard stdin and print to local "

@ -57,7 +57,7 @@ enum {
1 << 1, /* Throw away stdin and send to local stdout */
XMPPIPE_OPT_EOF = 1 << 2, /* Keep running on stdin EOF */
XMPPIPE_OPT_SIGPIPE = 1 << 3, /* Exit if no occupants in MUC */
XMPPIPE_OPT_GROUPCHAT = 1 << 4, /* Use groupchat */
XMPPIPE_OPT_GROUPCHAT = 1 << 4, /* Use groupchat */
};
enum { XMPPIPE_FMT_TEXT = 0, XMPPIPE_FMT_CSV };
@ -75,6 +75,8 @@ typedef struct {
char *subject; /* topic/subject for MUC */
char *out; /* room@conference.xmpp.example.com */
char *upload; /* XEP 0363 upload service */
int status;
int occupants;
u_int32_t poll; /* milliseconds */
@ -118,6 +120,8 @@ int handle_sm_ack(xmpp_conn_t *const, xmpp_stanza_t *const, void *const);
int handle_sm_enabled(xmpp_conn_t *const, xmpp_stanza_t *const, void *const);
int handle_sm_request(xmpp_conn_t *const, xmpp_stanza_t *const, void *const);
int handle_version(xmpp_conn_t *const, xmpp_stanza_t *const, void *const);
int handle_iq(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza,
void *const userdata);
void xmppipe_muc_join(xmppipe_state_t *);
void xmppipe_muc_subject(xmppipe_state_t *, char *);

@ -17,6 +17,9 @@
void xmppipe_send_oob(xmppipe_state_t *state, char *to, char *type, char *buf,
size_t len);
void xmppipe_send_http_upload(xmppipe_state_t *state, char *to, char *type,
char *buf, size_t len);
void xmppipe_send_stanza(xmppipe_state_t *state, char *buf, size_t len) {
switch (state->format) {
case XMPPIPE_FMT_TEXT:
@ -84,7 +87,8 @@ void xmppipe_send_stanza_fmt(xmppipe_state_t *state, char *buf, size_t len) {
switch (start[0]) {
case 'm': /* message */
case 'I': /* message: inline image using oob */
case 'I': /* message: iniline image using oob stanza */
case 'u': /* iq: http upload slot */
break;
case 'p':
/* unsupported: fall through */
@ -133,6 +137,9 @@ XMPPIPE_DONE:
case 'I':
xmppipe_send_oob(state, to, type, body, strlen(body));
break;
case 'u':
xmppipe_send_http_upload(state, to, type, body, strlen(body));
break;
case 'm':
default:
xmppipe_send_message(state, to, type, body, strlen(body));
@ -217,6 +224,87 @@ void xmppipe_send_oob(xmppipe_state_t *state, char *to, char *type, char *buf,
xmpp_free(state->ctx, id);
}
void xmppipe_send_http_upload(xmppipe_state_t *state, char *to, char *type,
char *buf, size_t len) {
xmpp_stanza_t *iq;
xmpp_stanza_t *request;
char *id;
char *filename = NULL;
char *size = NULL;
char *content_type = NULL;
int i;
char *start;
char *end;
if (state->upload == NULL) {
if (state->verbose)
(void)fprintf(stderr, "error: XEP 0363 http upload is not supported\n");
return;
}
id = xmpp_uuid_gen(state->ctx);
if (id == NULL) {
errx(EXIT_FAILURE, "unable to allocate message id");
}
start = buf;
/* <filename>|<size>|<content-type> */
for (i = 0; start != NULL; i++) {
end = strchr(start, '|');
if (end != NULL)
*end++ = '\0';
switch (i) {
case 0: /* filename */
filename = start;
break;
case 1: /* size */
size = start;
break;
case 2: /* content-type */
content_type = start;
break;
default:
/* invalid */
break;
}
start = end;
}
if (filename == NULL || size == NULL) {
if (state->verbose)
(void)fprintf(stderr, "error: invalid http upload request\n");
return;
}
iq = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(iq, "iq");
xmppipe_stanza_set_attribute(iq, "id", id);
xmppipe_stanza_set_attribute(iq, "type", "get");
xmppipe_stanza_set_attribute(iq, "to", state->upload);
request = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(request, "request");
xmppipe_stanza_set_ns(request, "urn:xmpp:http:upload:0");
xmppipe_stanza_set_attribute(request, "filename", filename);
xmppipe_stanza_set_attribute(request, "size", size);
if (content_type)
xmppipe_stanza_set_attribute(request, "content-type", content_type);
xmppipe_stanza_add_child(iq, request);
(void)xmpp_stanza_release(request);
xmppipe_send(state, iq);
(void)xmpp_stanza_release(iq);
xmpp_free(state->ctx, id);
}
void xmppipe_send(xmppipe_state_t *state, xmpp_stanza_t *const stanza) {
xmpp_stanza_t *r = NULL;

Loading…
Cancel
Save