diff -Nru purple-matrix-0.0.0+git20170530/debian/changelog purple-matrix-0.0.0+git20180325/debian/changelog --- purple-matrix-0.0.0+git20170530/debian/changelog 2017-11-14 14:00:15.000000000 +0000 +++ purple-matrix-0.0.0+git20180325/debian/changelog 2018-05-14 17:34:52.000000000 +0000 @@ -1,8 +1,14 @@ -purple-matrix (0.0.0+git20170530-1build1) bionic; urgency=medium +purple-matrix (0.0.0+git20180325-1build1) cosmic; urgency=medium * No-change rebuild for http-parser soname change. - -- Matthias Klose Tue, 14 Nov 2017 14:00:15 +0000 + -- Matthias Klose Mon, 14 May 2018 17:34:52 +0000 + +purple-matrix (0.0.0+git20180325-1) unstable; urgency=medium + + * New upstream snapshot (Closes: #894066). + + -- Alberto Garcia Mon, 26 Mar 2018 11:13:44 +0300 purple-matrix (0.0.0+git20170530-1) unstable; urgency=medium diff -Nru purple-matrix-0.0.0+git20170530/Makefile purple-matrix-0.0.0+git20180325/Makefile --- purple-matrix-0.0.0+git20170530/Makefile 2017-05-30 13:49:44.000000000 +0000 +++ purple-matrix-0.0.0+git20180325/Makefile 2018-03-25 16:17:32.000000000 +0000 @@ -6,7 +6,7 @@ PKG_CONFIG=pkg-config CFLAGS+=$(shell $(PKG_CONFIG) --cflags $(LIBS)) CFLAGS+=-fPIC -DPIC -LDLIBS+=$(shell pkg-config --libs $(LIBS)) +LDLIBS+=$(shell $(PKG_CONFIG) --libs $(LIBS)) LDLIBS+=-lhttp_parser PLUGIN_DIR_PURPLE = $(shell $(PKG_CONFIG) --variable=plugindir purple) diff -Nru purple-matrix-0.0.0+git20170530/matrix-api.c purple-matrix-0.0.0+git20180325/matrix-api.c --- purple-matrix-0.0.0+git20170530/matrix-api.c 2017-05-30 13:49:44.000000000 +0000 +++ purple-matrix-0.0.0+git20180325/matrix-api.c 2018-03-25 16:17:32.000000000 +0000 @@ -105,7 +105,7 @@ gchar *content_type; gboolean got_headers; JsonParser *json_parser; - const char *body; + char *body; size_t body_len; } MatrixApiResponseParserData; @@ -134,6 +134,9 @@ /* free the JSON parser, and all of the node structures */ if(data -> json_parser) g_object_unref(data -> json_parser); + g_free(data->body); + data->body = NULL; + g_free(data); } @@ -203,37 +206,47 @@ /** * callback from the http parser which handles the message body + * Can be called multiple times as we accumulate chunks. */ static int _handle_body(http_parser *http_parser, const char *at, size_t length) { MatrixApiResponseParserData *response_data = http_parser->data; - GError *err = NULL; - if(purple_debug_is_verbose()) purple_debug_info("matrixprpl", "Handling API response body %.*s\n", (int)length, at); + response_data->body = g_realloc(response_data->body, + response_data->body_len + length); + memcpy(response_data->body + response_data->body_len, at, length); + response_data->body_len += length; + + return 0; +} + +/** + * callback from the http parser after all chunks have arrived. + */ +static int _handle_message_complete(http_parser *http_parser) +{ + MatrixApiResponseParserData *response_data = http_parser->data; + GError *err = NULL; + if(strcmp(response_data->content_type, "application/json") == 0) { - if(!json_parser_load_from_data(response_data -> json_parser, at, length, + if(!json_parser_load_from_data(response_data -> json_parser, + response_data->body, + response_data->body_len, &err)) { purple_debug_info("matrixprpl", "unable to parse JSON: %s\n", err->message); g_error_free(err); return 1; } - } else { - /* Well if it's not JSON perhaps the callback is expecting to - * handle it itself, e.g. for an image. - */ - response_data->body = at; - response_data->body_len = length; } return 0; } - /** * The callback we give to purple_util_fetch_url_request - does some * initial processing of the response @@ -270,6 +283,7 @@ http_parser_settings.on_header_value = _handle_header_value; http_parser_settings.on_headers_complete = _handle_headers_complete; http_parser_settings.on_body = _handle_body; + http_parser_settings.on_message_complete = _handle_message_complete; http_parser_init(&http_parser, HTTP_RESPONSE); http_parser.data = response_data; @@ -668,7 +682,7 @@ * memory? But it's JSON */ fetch_data = matrix_api_start(url->str, "GET", NULL, conn, callback, - error_callback, bad_response_callback, user_data, 10*1024*1024); + error_callback, bad_response_callback, user_data, 40*1024*1024); g_string_free(url, TRUE); return fetch_data; @@ -774,9 +788,9 @@ MatrixApiRequestData *fetch_data; url = g_string_new(conn->homeserver); - g_string_append(url, "_matrix/client/r0/rooms/"); + g_string_append(url, "_matrix/client/r0/join/"); g_string_append(url, purple_url_encode(room)); - g_string_append(url, "/join?access_token="); + g_string_append(url, "?access_token="); g_string_append(url, purple_url_encode(conn->access_token)); purple_debug_info("matrixprpl", "joining %s\n", room); @@ -905,6 +919,20 @@ return fetch_data; } +GString *get_download_url(const gchar *homeserver, const gchar *uri) +{ + GString *url; + + /* Sanity check the uri - TODO: Add more sanity */ + if (strncmp(uri, "mxc://", 6)) { + return NULL; + } + url = g_string_new(homeserver); + g_string_append(url, "_matrix/media/r0/download/"); + g_string_append(url, uri + 6); /* i.e. after the mxc:// */ + return url; +} + /** * Download a file * @param uri URI string in the form mxc://example.com/unique @@ -920,14 +948,11 @@ GString *url; MatrixApiRequestData *fetch_data; - /* Sanity check the uri - TODO: Add more sanity */ - if (strncmp(uri, "mxc://", 6)) { + url = get_download_url(conn->homeserver, uri); + if (!url) { error_callback(conn, user_data, "bad media uri"); return NULL; } - url = g_string_new(conn->homeserver); - g_string_append(url, "_matrix/media/r0/download/"); - g_string_append(url, uri + 6); /* i.e. after the mxc:// */ /* I'd like to validate the headers etc a bit before downloading the * data (maybe using _handle_header_completed), also I'm not convinced diff -Nru purple-matrix-0.0.0+git20170530/matrix-api.h purple-matrix-0.0.0+git20180325/matrix-api.h --- purple-matrix-0.0.0+git20170530/matrix-api.h 2017-05-30 13:49:44.000000000 +0000 +++ purple-matrix-0.0.0+git20180325/matrix-api.h 2018-03-25 16:17:32.000000000 +0000 @@ -301,6 +301,15 @@ MatrixApiBadResponseCallback bad_response_callback, gpointer user_data); + +/* Get the complete download url for a given uri + * + * @param homeserver The server hosting the file + * @param uri The file uri + */ +GString *get_download_url(const gchar *homeserver, const gchar *uri); + + /** * Download a file * diff -Nru purple-matrix-0.0.0+git20170530/matrix-room.c purple-matrix-0.0.0+git20180325/matrix-room.c --- purple-matrix-0.0.0+git20170530/matrix-room.c 2017-05-30 13:49:44.000000000 +0000 +++ purple-matrix-0.0.0+git20180325/matrix-room.c 2018-03-25 16:17:32.000000000 +0000 @@ -21,10 +21,13 @@ /* stdlib */ #include #include +#include + +#include /* libpurple */ -#include "connection.h" -#include "debug.h" +#include +#include #include "libmatrix.h" #include "matrix-api.h" @@ -69,8 +72,12 @@ #define PURPLE_CONV_FLAGS "flags" #define PURPLE_CONV_FLAG_NEEDS_NAME_UPDATE 0x1 -/* Arbitrary limit on the size of an image to receive; should make configurable */ -static const size_t purple_max_image_size=250*1024; +/* Arbitrary limit on the size of an image to receive; should make + * configurable. This is based on the worst-case assumption of a + * 640x480 pixels, each with 3 bytes i.e. 900KiB. 640x480 is also the + * server generated thumbnail size. + */ +static const size_t purple_max_media_size=640*480*3; /** * Get the member table for a room @@ -228,12 +235,14 @@ if (new_user_found == FALSE) { roommember = matrix_roommembers_lookup_member(member_table, user_id); - displayname = matrix_roommember_get_displayname(roommember); + if (roommember) { + displayname = matrix_roommember_get_displayname(roommember); - // in old list, not in new, i.e. stopped typing - cbflags = purple_conv_chat_user_get_flags(chat, displayname); - cbflags &= ~PURPLE_CBFLAGS_TYPING; - purple_conv_chat_user_set_flags(chat, displayname, cbflags); + // in old list, not in new, i.e. stopped typing + cbflags = purple_conv_chat_user_get_flags(chat, displayname); + cbflags &= ~PURPLE_CBFLAGS_TYPING; + purple_conv_chat_user_set_flags(chat, displayname, cbflags); + } } } @@ -244,11 +253,13 @@ const gchar *displayname; roommember = matrix_roommembers_lookup_member(member_table, user_id); - displayname = matrix_roommember_get_displayname(roommember); + if (roommember) { + displayname = matrix_roommember_get_displayname(roommember); - cbflags = purple_conv_chat_user_get_flags(chat, displayname); - cbflags |= PURPLE_CBFLAGS_TYPING; - purple_conv_chat_user_set_flags(chat, displayname, cbflags); + cbflags = purple_conv_chat_user_get_flags(chat, displayname); + cbflags |= PURPLE_CBFLAGS_TYPING; + purple_conv_chat_user_set_flags(chat, displayname, cbflags); + } } } @@ -652,7 +663,7 @@ gchar *escaped_body = purple_markup_escape_text(rid->original_body, -1); serv_got_chat_in(rid->conv->account->gc, g_str_hash(rid->room_id), rid->sender_display_name, PURPLE_MESSAGE_RECV, - g_strdup_printf("%s (failed to download %d)", + g_strdup_printf("%s (bad response to download image %d)", escaped_body, http_response_code), rid->timestamp / 1000); purple_conversation_set_data(rid->conv, PURPLE_CONV_DATA_ACTIVE_SEND, @@ -669,7 +680,7 @@ gchar *escaped_body = purple_markup_escape_text(rid->original_body, -1); serv_got_chat_in(rid->conv->account->gc, g_str_hash(rid->room_id), rid->sender_display_name, PURPLE_MESSAGE_RECV, - g_strdup_printf("%s (failed to download %s)", + g_strdup_printf("%s (failed to download image %s)", escaped_body, error_message), rid->timestamp / 1000); purple_conversation_set_data(rid->conv, PURPLE_CONV_DATA_ACTIVE_SEND, NULL); @@ -680,93 +691,137 @@ /* - * Called from matrix_room_handle_timeline_event when it finds an m.image; - * msg_body has the fallback text, + * Called from matrix_room_handle_timeline_event when it finds an m.video + * or m.audio or m.file or m.image; msg_body has the fallback text, * json_content_object has the json for the content sub object - * - * Return TRUE if we managed to download the image and everything needed - * FALSE if we failed; caller does fallback. */ -static gboolean _handle_incoming_image(PurpleConversation *conv, +static gboolean _handle_incoming_media(PurpleConversation *conv, const gint64 timestamp, const gchar *room_id, const gchar *sender_display_name, const gchar *msg_body, - JsonObject *json_content_object) { + JsonObject *json_content_object, const gchar *msg_type) { MatrixConnectionData *conn = _get_connection_data_from_conversation(conv); MatrixApiRequestData *fetch_data = NULL; - struct ReceiveImageData *rid; - gboolean use_thumb = FALSE; const gchar *url; + GString *download_url; + guint64 size = 0; + const gchar *mime_type = "unknown"; JsonObject *json_info_object; url = matrix_json_object_get_string_member(json_content_object, "url"); if (!url) { /* That seems odd, oh well, no point in getting upset */ - purple_debug_info("matrixprpl", "failed to get url for m.image"); + purple_debug_info("matrixprpl", "failed to get url for media\n"); + return FALSE; + } + download_url = get_download_url(conn->homeserver, url); + if (!download_url) { + purple_debug_error("matrixprpl", "failed to get download_url for media\n"); return FALSE; } - /* the 'info' member is optional but if we've got it we can check it to early - * reject the image if it's something that's huge or we don't know the title. - */ + /* the 'info' member is optional */ json_info_object = matrix_json_object_get_object_member(json_content_object, "info"); - purple_debug_info("matrixprpl", "%s: %s json_info_object=%p\n", __func__, - url, json_info_object); if (json_info_object) { - guint64 size; - const gchar *mime_type; - - /* OK, we've got some (optional) info on the image */ + /* OK, we've got some (optional) info */ size = matrix_json_object_get_int_member(json_info_object, "size"); - if (size > purple_max_image_size) { - use_thumb = TRUE; - } mime_type = matrix_json_object_get_string_member(json_info_object, "mimetype"); - if (mime_type) { - if (!is_known_image_type(mime_type)) { - purple_debug_info("matrixprpl", "%s: unknown mimetype %s", - __func__, mime_type); - return FALSE; - } - } - purple_debug_info("matrixprpl", "image info good: %s of %" PRId64, + purple_debug_info("matrixprpl", "media info good: %s of %" PRId64 "\n", mime_type, size); } - rid = g_new0(struct ReceiveImageData, 1); - rid->conv = conv; - rid->timestamp = timestamp; - rid->sender_display_name = sender_display_name; - rid->room_id = room_id; - rid->original_body = g_strdup(msg_body); - - if (!use_thumb) { - fetch_data = matrix_api_download_file(conn, url, - purple_max_image_size, + serv_got_chat_in(conv->account->gc, g_str_hash(room_id), + sender_display_name, PURPLE_MESSAGE_RECV, + /* TODO convert size into a human readable format */ + g_strdup_printf("%s (type %s size %" PRId64 ") %s", + msg_body, mime_type, size, download_url->str), timestamp / 1000); + + /* m.audio is not supposed to have a thumbnail, handling completed + */ + if (!strcmp("m.audio", msg_type)) { + return TRUE; + } + + /* If a thumbnail_url is available and the thumbnail size is small, + * download that. Otherwise, only for m.image, ask for a server generated + * thumbnail. + */ + int is_image = !strcmp("m.image", msg_type); + const gchar *thumb_url = ""; + JsonObject *json_thumb_info; + guint64 thumb_size = 0; + /* r0.2.0 -> r0.3.0 + * m.image content.thumb* -> content.info.thumb* + * m.video - + * m.file content.thumb* -> content.info.thumb* + */ + thumb_url = matrix_json_object_get_string_member(json_info_object, "thumbnail_url"); + json_thumb_info = matrix_json_object_get_object_member(json_info_object, "thumbnail_info"); + if (json_thumb_info) { + thumb_size = matrix_json_object_get_int_member(json_thumb_info, "size"); + } else { + /* m.image and m.file had thumbnail_* members directly in the content object prior to r0.3.0 */ + thumb_url = matrix_json_object_get_string_member(json_content_object, "thumbnail_url"); + json_thumb_info = matrix_json_object_get_object_member(json_content_object, "thumbnail_info"); + if (json_thumb_info) { + thumb_size = matrix_json_object_get_int_member(json_thumb_info, "size"); + } + } + if (is_image && (size > 0) && (size < purple_max_media_size)) { + /* if an m.image is small, get that instead of the thumbnail */ + thumb_url = url; + thumb_size = size; + } + if (thumb_url || is_image) { + struct ReceiveImageData *rid; + rid = g_new0(struct ReceiveImageData, 1); + rid->conv = conv; + rid->timestamp = timestamp; + rid->sender_display_name = sender_display_name; + rid->room_id = room_id; + rid->original_body = g_strdup(msg_body); + + if (thumb_url && (thumb_size > 0) && (thumb_size < purple_max_media_size)) { + fetch_data = matrix_api_download_file(conn, thumb_url, + purple_max_media_size, _image_download_complete, _image_download_error, - _image_download_bad_response, rid); - } else { - /* TODO: Configure the size of thumbnails, and provide - * a way for the user to get the full image if they want. + _image_download_bad_response, + rid); + } else if (thumb_url) { + /* Ask the server to generate a thumbnail of the thumbnail. + * Useful to improve the chance of showing something when the + * original thumbnail is too big. + */ + fetch_data = matrix_api_download_thumb(conn, thumb_url, + purple_max_media_size, + 640, 480, TRUE, /* Scaled */ + _image_download_complete, + _image_download_error, + _image_download_bad_response, + rid); + } else { + /* Ask the server to generate a thumbnail. Only for m.image. + * TODO: Configure the size of thumbnails. * 640x480 is a good a width as any and reasonably likely to * fit in the byte size limit unless someone has a big long * tall png. */ fetch_data = matrix_api_download_thumb(conn, url, - purple_max_image_size, + purple_max_media_size, 640, 480, TRUE, /* Scaled */ _image_download_complete, _image_download_error, - _image_download_bad_response, rid); + _image_download_bad_response, + rid); + } + purple_conversation_set_data(conv, PURPLE_CONV_DATA_ACTIVE_SEND, + fetch_data); + return fetch_data != NULL; } - - purple_conversation_set_data(conv, PURPLE_CONV_DATA_ACTIVE_SEND, - fetch_data); - - return fetch_data != NULL; + return TRUE; } /** @@ -937,12 +992,13 @@ if (!strcmp(msg_type, "m.emote")) { tmp_body = g_strdup_printf("/me %s", msg_body); - } else if (!strcmp(msg_type, "m.image")) { - if (_handle_incoming_image(conv, timestamp, room_id, sender_display_name, - msg_body, json_content_obj)) { + } else if ((!strcmp(msg_type, "m.video")) || (!strcmp(msg_type, "m.audio")) || \ + (!strcmp(msg_type, "m.file")) || (!strcmp(msg_type, "m.image"))) { + if (_handle_incoming_media(conv, timestamp, room_id, sender_display_name, + msg_body, json_content_obj, msg_type)) { return; } - /* Fall through - we couldn't get the image, treat as text */ + /* Fall through - we couldn't handle the media, treat as text */ } flags = PURPLE_MESSAGE_RECV; diff -Nru purple-matrix-0.0.0+git20170530/README.md purple-matrix-0.0.0+git20180325/README.md --- purple-matrix-0.0.0+git20170530/README.md 2017-05-30 13:49:44.000000000 +0000 +++ purple-matrix-0.0.0+git20180325/README.md 2018-03-25 16:17:32.000000000 +0000 @@ -20,19 +20,29 @@ * Account registration * Room topics * Voice/video calling + * End-To-End encryption via Olm ([ticket](https://github.com/matrix-org/purple-matrix/issues/18)) The plugin requires a matrix homeserver supporting client-server API r0.0.0 Synapse v0.12.0-rc1 or later is sufficient. # Installation -Currently there are no pre-built binaries, so the plugin needs to be built +Pre-built binaries are available for Ubuntu since version 17.04 (Zesty Zapus). +You should be able to install them giving the following commands in a terminal +window: + +``` +sudo apt update +sudo apt install purple-matrix +``` + +For other GNU/Linux systems the plugin needs to be built from source. You will need development headers/libraries for the following: * libpurple 2.x [libpurple-dev] * libjson-glib [libjson-glib-dev] -* libglib [libglib-dev] +* libglib [libglib-dev (or libglib2.0-dev on Ubuntu 16.04 xenial)] * libhttp_parser [libhttp-parser-dev]. You should then be able to: