From af87f753387f6af8e484f3b5ae0d6a118986fbad Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Tue, 28 Nov 2017 18:09:55 +0100 Subject: [PATCH] Port MatrixHTTPAPI to C --- .gitignore | 1 - src/Makefile.am | 3 +- src/matrix-http-api.c | 2627 ++++++++++++++++++++++++++++++++++++++ src/matrix-http-api.h | 58 + src/matrix-http-api.vala | 1913 --------------------------- src/test-api-client.c | 2 +- vapi/c-api.vapi | 244 ++++ 7 files changed, 2932 insertions(+), 1916 deletions(-) create mode 100644 src/matrix-http-api.c create mode 100644 src/matrix-http-api.h delete mode 100644 src/matrix-http-api.vala diff --git a/.gitignore b/.gitignore index 5d26349..bbada99 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,6 @@ Makefile.in /src/vala-stamp /src/matrix-glib.h /src/matrix-client.c -/src/matrix-http-api.c /src/matrix-http-client.c /src/namespace-info.vala /src/namespace-info.c diff --git a/src/Makefile.am b/src/Makefile.am index da6331d..8f59e26 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -18,7 +18,6 @@ lib_LTLIBRARIES = libmatrix-glib-0.0.la libmatrix_glib_0_0_la_VALA_SOURCES = \ namespace-info.vala \ matrix-client.vala \ - matrix-http-api.vala \ matrix-http-client.vala \ $(NULL) @@ -76,6 +75,7 @@ INST_H_SRC_FILES = \ matrix-types.h \ matrix-compacts.h \ matrix-api.h \ + matrix-http-api.h \ matrix-event-base.h \ matrix-event-call-base.h \ matrix-event-call-answer.h \ @@ -134,6 +134,7 @@ libmatrix_glib_0_0_la_SOURCES = \ matrix-event-types.c \ matrix-version.c \ matrix-api.c \ + matrix-http-api.c \ matrix-types.c \ matrix-compacts.c \ matrix-event-base.c \ diff --git a/src/matrix-http-api.c b/src/matrix-http-api.c new file mode 100644 index 0000000..ce2de33 --- /dev/null +++ b/src/matrix-http-api.c @@ -0,0 +1,2627 @@ +/* + * This file is part of matrix-glib-sdk + * + * matrix-glib-sdk is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * matrix-glib-sdk is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with matrix-glib-sdk. If not, see + * . + */ + +#include +#include +#include +#include "matrix-http-api.h" +#include "matrix-enumtypes.h" +#include "config.h" +#include "utils.h" +#include "matrix-compacts.h" +#include "matrix-event-state-base.h" +#include "matrix-event-base.h" + +/** + * SECTION:matrix-http-api + * @short_description: low-level API to communicate with homeservers via HTTP + * + * This is a class for low level communication with a Matrix.org server via HTTP. + */ +enum { + PROP_0, + PROP_BASE_URL, + PROP_VALIDATE_CERTIFICATE, + PROP_USER_ID, + PROP_TOKEN, + PROP_REFRESH_TOKEN, + PROP_HOMESERVER, + NUM_PROPERTIES +}; + +static GParamSpec *matrix_http_api_properties[NUM_PROPERTIES]; + +typedef struct { + SoupSession *soup_session; + gchar *base_url; + SoupURI *api_uri; + SoupURI *media_uri; + gchar *token; + gchar *refresh_token; +} MatrixHTTPAPIPrivate; + +static void matrix_http_api_matrix_api_interface_init(MatrixAPIInterface * iface); + +G_DEFINE_TYPE_EXTENDED(MatrixHTTPAPI, matrix_http_api, G_TYPE_OBJECT, 0, G_ADD_PRIVATE(MatrixHTTPAPI) G_IMPLEMENT_INTERFACE(MATRIX_TYPE_API, matrix_http_api_matrix_api_interface_init)); + +typedef enum { + CALL_TYPE_API, + CALL_TYPE_MEDIA +} CallType; + +#define API_ENDPOINT "/_matrix/client/r0/" +#define MEDIA_ENDPOINT "/_matrix/media/r0/" + +#define string_get(s, i) (((s) == NULL) ? 0 : s[i]) +#define string_offset(s, o) (((s) == NULL) ? NULL : (s) + o) +#define uri_encode(s) gchar *enc_ ## s = soup_uri_encode(s, NULL) + +static void +_matrix_http_api_set_url(MatrixHTTPAPI *matrix_http_api, SoupURI **uri, const gchar *base_url, const gchar *endpoint) +{ + gchar *url; + SoupURI *new_uri; + + g_return_if_fail(matrix_http_api != NULL); + g_return_if_fail(base_url != NULL); + g_return_if_fail(endpoint != NULL); + g_return_if_fail(uri != NULL); + + if (string_get(base_url, strlen(base_url) - 1) == '/') { + url = g_strdup_printf("%s%s", base_url, string_offset(endpoint, 1)); + } else { + url = g_strdup_printf("%s%s", base_url, endpoint); + } + + new_uri = soup_uri_new(url); + + if (SOUP_URI_VALID_FOR_HTTP(new_uri)) { + *uri = new_uri; + } else { + *uri = NULL; + } + g_free(url); +} + +/** + * matrix_http_api_new: + * @base_url: the base URL of the homeserver to use + * @token: an access token to use + * @refresh_token: a refresh token to use + * + * Create a new #MatrixHTTPAPI object. + * + * Returns: (transfer full): a new #MatrixHTTPAPI object + */ +MatrixHTTPAPI * +matrix_http_api_new(const gchar *base_url, const gchar *token, const gchar *refresh_token) +{ + MatrixHTTPAPI *ret; + MatrixHTTPAPIPrivate *priv; + + g_return_val_if_fail(base_url != NULL, NULL); + + ret = (MatrixHTTPAPI*) g_object_new(MATRIX_TYPE_HTTP_API, + "base-url", base_url, + "token", token, + "refresh-token", refresh_token, + NULL); + priv = matrix_http_api_get_instance_private(ret); + + g_object_set(priv->soup_session, "ssl-strict", TRUE, NULL); + + return ret; +} + +static GHashTable * +_matrix_http_api_create_query_params(void) +{ + return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); +} + +typedef struct { + MatrixHTTPAPI *matrix_http_api; + MatrixAPICallback cb; + CallType call_type; + gboolean accept_non_json; + gpointer cb_target; + guint refcount; +} SendCallbackData; + +static void +_matrix_http_api_response_callback(SoupSession *session, SoupMessage *msg, gpointer user_data) +{ + SendCallbackData *callback_data = user_data; + MatrixHTTPAPI *matrix_http_api = callback_data->matrix_http_api; + MatrixHTTPAPIPrivate *priv = matrix_http_api_get_instance_private(matrix_http_api); + CallType call_type = callback_data->call_type; + gboolean accept_non_json = callback_data->accept_non_json; + MatrixAPICallback cb = callback_data->cb; + void *cb_target = callback_data->cb; + SoupURI *request_uri = soup_message_get_uri(msg); + const gchar *request_url = soup_uri_get_path(request_uri); + GError *err = NULL; + GByteArray *raw_content = NULL; + JsonNode *content = NULL; + + switch (call_type) { + case CALL_TYPE_API: + request_url = g_strdup(request_url + strlen(API_ENDPOINT)); + + break; + case CALL_TYPE_MEDIA: + request_url = g_strdup(request_url + strlen(MEDIA_ENDPOINT)); + + break; + } + + if ((msg->status_code < 100) || (msg->status_code >= 400)) { + err = g_error_new(MATRIX_ERROR, MATRIX_ERROR_COMMUNICATION_ERROR, + "%s %u: %s", + (msg->status_code < 100) ? "Network error" : "HTTP", + msg->status_code, + msg->reason_phrase); + } else { + SoupBuffer *buffer = soup_message_body_flatten(msg->response_body); + gsize datalen = buffer->length; + JsonParser *parser = json_parser_new(); + gboolean is_json; + + is_json = json_parser_load_from_data(parser, buffer->data, (gssize)buffer->length, NULL); + + if (is_json) { +#if DEBUG + g_debug("Response (%s): %s", request_url, buffer->data); +#endif + + content = json_parser_get_root(parser); + + if (json_node_get_node_type(content) == JSON_NODE_OBJECT) { + JsonObject *root = json_node_get_object(content); + JsonNode *node; + JsonNode *errcode_node; + JsonNode *error_node; + + /* Check if the response holds an access token; if it + * does, set it as our new token */ + if ((node = json_object_get_member(root, "access_token")) != NULL) { + const gchar *access_token; + + if ((access_token = json_node_get_string(node)) != NULL) { +#if DEBUG + g_debug("Got new access token: %s", access_token); +#endif + + g_free(priv->token); + priv->token = g_strdup(access_token); + } + } + + /* Check if the response holds a refresh token; if it + * does, set it as our new refresh token */ + if ((node = json_object_get_member(root, "refresh_token")) != NULL) { + const gchar *refresh_token; + + if ((refresh_token = json_node_get_string(node)) != NULL) { +#if DEBUG + g_debug("Got new refresh token: %s", refresh_token); +#endif + + g_free(priv->refresh_token); + priv->refresh_token = g_strdup(refresh_token); + } + } + + /* Check if the response holds a homeserver name */ + if ((node = json_object_get_member(root, "home_server")) != NULL) { + const gchar *homeserver = json_node_get_string(node); + +#if DEBUG + g_debug("Our home server calls itself %s", homeserver); +#endif + + g_free(matrix_http_api->_homeserver); + matrix_http_api->_homeserver = g_strdup(homeserver); + } + + /* Check if the response holds a user ID; if it does, + * set this as our user ID */ + if ((node = json_object_get_member(root, "user_id")) != NULL) { + const gchar *user_id = json_node_get_string(node); + +#if DEBUG + g_debug("We are reported to be logged in as %s", user_id); +#endif + + g_free(matrix_http_api->_user_id); + matrix_http_api->_user_id = g_strdup(user_id); + } + + /* Check if the response holds an error */ + errcode_node = json_object_get_member(root, "errcode"); + error_node = json_object_get_member(root, "error"); + const gchar *error = NULL; + const gchar *errcode = NULL; + + if ((errcode_node != NULL) || (error_node != NULL)) { + err = g_error_new_literal(MATRIX_ERROR, MATRIX_ERROR_UNKNOWN_ERROR, + "The error is not known to this library"); + + if (error_node != NULL) { + error = json_node_get_string(error_node); + } + + if (errcode_node != NULL) { + errcode = json_node_get_string(errcode_node); + + if (g_ascii_strncasecmp(errcode, "M_", 2) == 0) { + gchar *errcode_enum_name = g_strdup_printf("MATRIX_ERROR_%s", errcode); + GEnumClass *error_class = g_type_class_ref(MATRIX_TYPE_ERROR); + GEnumValue *enum_value = g_enum_get_value_by_name(error_class, errcode_enum_name); + g_type_class_unref(error_class); + g_free(errcode_enum_name); + + if (enum_value != NULL) { + err->code = enum_value->value; + } else { + g_warning("An unknown error code '%s' was sent by the homeserver. You may want to report it to the Matrix GLib developers", errcode); + } + } + } else { + g_warning("An error was sent by the homeserver, but no error code was specified. You may want to report this to the homeserver admins."); + g_clear_error(&err); + + err = g_error_new_literal(MATRIX_ERROR, MATRIX_ERROR_UNSPECIFIED, "No error code was sent by the server"); + } + + if ((errcode_node != NULL) && (error_node != NULL)) { + err->message = g_strdup_printf("%s: %s", errcode, error); + } else if (errcode_node != NULL) { + err->message = g_strdup(errcode); + } else { + err->message = g_strdup_printf("(No errcode given) %s", error); + } + } + } else if (json_node_get_node_type(content) != JSON_NODE_ARRAY) { + err = g_error_new_literal(MATRIX_ERROR, MATRIX_ERROR_BAD_RESPONSE, + "Bad response: not a JSON object, nor an array."); + } + } else { + if (accept_non_json) { + raw_content = g_byte_array_sized_new((uint)datalen); + g_byte_array_append(raw_content, (guint8 *)buffer->data, buffer->length); + +#if DEBUG + g_debug("Binary data (%s): %u bytes", request_url, (uint)datalen); +#endif + } else { + err = g_error_new_literal(MATRIX_ERROR, MATRIX_ERROR_BAD_RESPONSE, + "Malformed response (invalid JSON)"); + +#if DEBUG + g_debug("Malformed response (%s): %s", request_url, data); +#endif + } + } + } + + /* Call the assigned function, if any */ + if (cb != NULL) { + cb(MATRIX_API(matrix_http_api), + soup_message_headers_get_content_type(msg->response_headers, NULL), + content, raw_content, + err, + cb_target); + } + + g_free(callback_data); +} + +static void +_matrix_http_api_send(MatrixHTTPAPI *matrix_http_api, + MatrixAPICallback cb, + void *cb_target, + CallType call_type, + const gchar *method, + const gchar *path, + GHashTable *parms, + const gchar *content_type, + JsonNode *json_content, + GByteArray *raw_content, + gboolean accept_non_json, + GError **error) +{ + MatrixHTTPAPIPrivate *priv; + SoupMessage *message; + SoupURI *request_path = NULL; + gpointer request_data; + gsize request_len; + SendCallbackData *callback_data; + + g_return_if_fail(matrix_http_api != NULL); + g_return_if_fail(method != NULL); + g_return_if_fail(path != NULL); + + priv = matrix_http_api_get_instance_private(matrix_http_api); + + if ((priv->api_uri == NULL) || (priv->media_uri == NULL)) { + g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_COMMUNICATION_ERROR, "No valid base URL"); + + return; + } + + if ((json_content != NULL) && (raw_content != NULL)) { + g_critical("json_content and raw_content cannot be used together. This is a bug."); + } + + if (!g_str_is_ascii(method)) { + g_critical("Method must be ASCII. This is a bug."); + } + + if ((g_ascii_strcasecmp(method, "GET") != 0) && + (g_ascii_strcasecmp(method, "POST") != 0) && + (g_ascii_strcasecmp(method, "PUT") != 0) && + (g_ascii_strcasecmp(method, "DELETE") != 0)) { + g_critical("Method %s is invalid. This is a bug.", method); + } + + if (call_type == CALL_TYPE_MEDIA) { + request_path = soup_uri_new_with_base(priv->media_uri, path); + } else { + request_path = soup_uri_new_with_base(priv->api_uri, path); + } + + if (parms == NULL) { + parms = _matrix_http_api_create_query_params(); + } + + if (priv->token != NULL) { +#if DEBUG + g_debug("Adding access token '%s'", priv->token); +#endif + + g_hash_table_replace(parms, g_strdup("access_token"), priv->token); + } + + soup_uri_set_query_from_form(request_path, parms); + + message = soup_message_new_from_uri(method, request_path); + + if (json_content != NULL) { + JsonGenerator *generator = json_generator_new(); + + json_generator_set_root(generator, json_content); + request_data = json_generator_to_data(generator, &request_len); + } else if (raw_content != NULL) { + request_len = raw_content->len; + request_data = raw_content->data; + } else { + request_len = 2; + request_data = g_strdup("{}"); + } + +#if DEBUG + g_debug("Sending %lu bytes (%s %s): %s", + request_len, + method, + soup_uri_to_string(request_path, FALSE), // TODO: Free this! + (raw_content != NULL) ? "" : (gchar *)request_data); +#endif + + soup_message_set_flags(message, SOUP_MESSAGE_NO_REDIRECT); + soup_message_set_request(message, + (content_type == NULL) ? "application/json" : content_type, + SOUP_MEMORY_COPY, + request_data, + request_len); + + callback_data = g_new0(SendCallbackData, 1); + + callback_data->matrix_http_api = matrix_http_api; + callback_data->refcount = 1; + callback_data->cb = cb; + callback_data->cb_target = cb_target; + callback_data->call_type = call_type; + callback_data->accept_non_json = accept_non_json; + + soup_session_queue_message(priv->soup_session, message, _matrix_http_api_response_callback, callback_data); +} + +static void +matrix_http_api_media_download(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *server_name, const gchar *media_id, GError **error) +{ + gchar *path; + + g_return_if_fail(server_name != NULL); + g_return_if_fail(media_id != NULL); + + uri_encode(server_name); + uri_encode(media_id); + path = g_strconcat("download/", enc_server_name, "/", enc_media_id, NULL); + g_free(enc_server_name); + g_free(enc_media_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_MEDIA, "GET", path, NULL, NULL, NULL, NULL, TRUE, error); + + g_free(path); +} + +static void +matrix_http_api_media_thumbnail(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *server_name, const gchar *media_id, guint width, guint height, MatrixResizeMethod method, GError **error) +{ + gchar *path; + GHashTable *parms; + + g_return_if_fail(server_name != NULL); + g_return_if_fail(media_id != NULL); + + uri_encode(server_name); + uri_encode(media_id); + path = g_strconcat("download/", enc_server_name, "/", enc_media_id, NULL); + g_free(enc_server_name); + g_free(enc_media_id); + + parms = _matrix_http_api_create_query_params(); + + if (width > 0) { + g_hash_table_replace(parms, g_strdup("width"), g_strdup_printf("%u", width)); + } + + if (height > 0) { + g_hash_table_replace(parms, g_strdup("height"), g_strdup_printf("%u", height)); + } + + if (method != MATRIX_RESIZE_METHOD_DEFAULT) { + switch (method) { + case MATRIX_RESIZE_METHOD_CROP: + g_hash_table_replace(parms, g_strdup("method"), g_strdup("crop")); + + break; + case MATRIX_RESIZE_METHOD_SCALE: + g_hash_table_replace(parms, g_strdup("method"), g_strdup("scale")); + + break; + // This is here only to prevent compiler warnings + case MATRIX_RESIZE_METHOD_DEFAULT: break; + } + } + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_MEDIA, "GET", path, parms, NULL, NULL, NULL, TRUE, error); + + g_free(path); + g_hash_table_unref(parms); +} + +static void +matrix_http_api_media_upload(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *content_type, GByteArray *content, GError **error) +{ + g_return_if_fail(content != NULL); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_MEDIA, "POST", "upload", NULL, content_type, NULL, content, FALSE, error); +} + +static void +matrix_http_api_get_presence_list(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, GError **error) +{ + gchar *path; + + g_return_if_fail(user_id != NULL); + + uri_encode(user_id); + path = g_strconcat("presence/list/", enc_user_id, NULL); + g_free(enc_user_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "GET", path, NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_update_presence_list(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, gchar **drop_ids, int n_drop_ids, gchar **invite_ids, int n_invite_ids, GError **error) +{ + gchar *path; + JsonBuilder *builder; + JsonNode *root_node; + + g_return_if_fail(user_id != NULL); + + uri_encode(user_id); + path = g_strconcat("presence/", enc_user_id, "/status", NULL); + g_free(enc_user_id); + + builder = json_builder_new(); + json_builder_begin_object(builder); + + if (n_drop_ids > 0) { + json_builder_set_member_name(builder, "drop"); + json_builder_begin_array(builder); + + for (gint i = 0; i < n_drop_ids; i++) { + json_builder_add_string_value(builder, drop_ids[i]); + } + + json_builder_end_array(builder); + } + + if (n_invite_ids > 0) { + json_builder_set_member_name(builder, "invite"); + json_builder_begin_array(builder); + + for (gint i = 0; i < n_invite_ids; i++) { + json_builder_add_string_value(builder, invite_ids[i]); + } + + json_builder_end_array(builder); + } + + json_builder_end_object(builder); + + root_node = json_builder_get_root(builder); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "POST", path, NULL, NULL, root_node, NULL, FALSE, error); + + g_free(path); + json_node_unref(root_node); + g_object_unref(builder); +} + +static void +matrix_http_api_get_presence(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, GError **error) +{ + gchar *path; + + g_return_if_fail(user_id != NULL); + + uri_encode(user_id); + path = g_strconcat("presence/", enc_user_id, "/status", NULL); + g_free(enc_user_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "GET", path, NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_set_presence(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, MatrixPresence presence, const gchar *status_message, GError **error) +{ + gchar *path; + gchar *presence_str; + JsonBuilder *builder; + JsonNode *root_node; + + g_return_if_fail(user_id != NULL); + + uri_encode(user_id); + path = g_strconcat("presence/", enc_user_id, "/status", NULL); + g_free(enc_user_id); + + builder = json_builder_new(); + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "presence"); + presence_str = _matrix_g_enum_to_string(MATRIX_TYPE_PRESENCE, presence, '_'); + json_builder_add_string_value(builder, presence_str); + g_free(presence_str); + + if (status_message != NULL) { + json_builder_set_member_name(builder, "status_msg"); + json_builder_add_string_value(builder, status_message); + } + + json_builder_end_object(builder); + + root_node = json_builder_get_root(builder); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "POST", path, NULL, NULL, root_node, NULL, FALSE, error); + + json_node_unref(root_node); + g_object_unref(builder); + g_free(path); +} + +static void +matrix_http_api_update_pusher(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, MatrixPusher *pusher, GError **error) +{ + JsonNode *pusher_node; + GError *inner_error = NULL; + + g_return_if_fail(pusher != NULL); + + pusher_node = matrix_json_compact_get_json_node(MATRIX_JSON_COMPACT(pusher), &inner_error); + + if (inner_error != NULL) { + g_propagate_error(error, inner_error); + return; + } + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "POST", "pushers/set", NULL, NULL, pusher_node, NULL, FALSE, error); + + json_node_unref(pusher_node); +} + +static void +matrix_http_api_get_pushers(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, GError **error) +{ + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "GET", "pushrules", NULL, NULL, NULL, NULL, FALSE, error); +} + +static gchar * +_matrix_http_api_pusher_url(const gchar *scope, MatrixPusherKind kind, const gchar *rule_id) +{ + gchar *kind_str; + gchar *url; + + g_return_val_if_fail(scope != NULL, NULL); + g_return_val_if_fail(rule_id != NULL, NULL); + + uri_encode(scope); + kind_str = _matrix_g_enum_to_string(MATRIX_TYPE_PUSHER_KIND, kind, '_'); + uri_encode(kind_str); + g_free(kind_str); + uri_encode(rule_id); + url = g_strconcat("pushrules/", enc_scope, "/", enc_kind_str, "/", enc_rule_id, NULL); + g_free(enc_scope); + g_free(enc_kind_str); + g_free(enc_rule_id); + + return url; +} + +static void +_matrix_http_api_pusher_modif(MatrixHTTPAPI *matrix_http_api, MatrixAPICallback cb, void *cb_target, const gchar *method, const gchar *scope, MatrixPusherKind kind, const gchar *rule_id, GError **error) +{ + gchar *path; + + g_return_if_fail(matrix_http_api != NULL); + g_return_if_fail(method != NULL); + g_return_if_fail(scope != NULL); + g_return_if_fail(rule_id != NULL); + + path = _matrix_http_api_pusher_url(scope, kind, rule_id); + + _matrix_http_api_send(matrix_http_api, cb, cb_target, CALL_TYPE_API, method, path, NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_delete_pushrule(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *scope, MatrixPusherKind kind, const gchar *rule_id, GError **error) +{ + g_return_if_fail(scope != NULL); + g_return_if_fail(rule_id != NULL); + + _matrix_http_api_pusher_modif(MATRIX_HTTP_API(matrix_api), cb, cb_target, "DELETE", scope, kind, rule_id, error); +} + +static void +matrix_http_api_get_pushrule(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *scope, MatrixPusherKind kind, const gchar *rule_id, GError **error) +{ + g_return_if_fail(scope != NULL); + g_return_if_fail(rule_id != NULL); + + _matrix_http_api_pusher_modif(MATRIX_HTTP_API(matrix_api), cb, cb_target, "GET", scope, kind, rule_id, error); +} + +static void +matrix_http_api_add_pushrule(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *scope, MatrixPusherKind kind, const gchar *rule_id, const gchar *before, const gchar *after, gchar **actions, int n_actions, MatrixPusherConditionKind *conditions, int n_conditions, GError **error) +{ + JsonBuilder *builder; + GHashTable *parms; + gchar *path; + JsonNode *root_node; + + g_return_if_fail(scope != NULL); + g_return_if_fail(rule_id != NULL); + + parms = _matrix_http_api_create_query_params(); + + if (before != NULL) { + g_hash_table_replace(parms, g_strdup("before"), g_strdup(before)); + } + + if (after != NULL) { + g_hash_table_replace(parms, g_strdup("after"), g_strdup(after)); + } + + builder = json_builder_new(); + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "actions"); + json_builder_begin_array(builder); + + for (gint i = 0; i < n_actions; i++) { + json_builder_add_string_value(builder, actions[i]); + } + + json_builder_end_array(builder); + + if (n_conditions > 0) { + json_builder_set_member_name(builder, "conditions"); + json_builder_begin_array(builder); + + for (gint i = 0; i < n_conditions; i++) { + gchar *kind_str = _matrix_g_enum_to_string(MATRIX_TYPE_PUSHER_CONDITION_KIND, conditions[i], '_'); + + if (kind_str == NULL) { + g_warning("Skipping invalid condition kind %d", conditions[i]); + + continue; + } + + json_builder_begin_object(builder); + json_builder_set_member_name(builder, "kind"); + json_builder_add_string_value(builder, kind_str); + json_builder_end_object(builder); + } + + json_builder_end_array(builder); + } + + json_builder_end_object(builder); + root_node = json_builder_get_root(builder); + + path = _matrix_http_api_pusher_url(scope, kind, rule_id); + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "GET", path, parms, NULL, root_node, NULL, FALSE, error); + + g_free(path); + json_node_unref(root_node); + g_object_unref(builder); +} + + +static void +matrix_http_api_toggle_pushrule(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *scope, MatrixPusherKind kind, const gchar *rule_id, gboolean enabled, GError **error) +{ + JsonBuilder *builder; + JsonNode *root_node; + gchar *path; + + g_return_if_fail(scope != NULL); + g_return_if_fail(rule_id != NULL); + + builder = json_builder_new(); + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "enabled"); + json_builder_add_boolean_value(builder, enabled); + + json_builder_end_object(builder); + root_node = json_builder_get_root(builder); + + path = _matrix_http_api_pusher_url(scope, kind, rule_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "GET", path, NULL, NULL, root_node, NULL, FALSE, error); + + g_free(path); + json_node_unref(root_node); + g_object_unref(builder); +} + +static void +matrix_http_api_get_pushrules(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, GError **error) +{ + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "GET", "pushrules", NULL, NULL, NULL, NULL, FALSE, error); +} + +static void +matrix_http_api_create_room(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, MatrixRoomPreset preset, const gchar *room_name, const gchar *room_alias, const gchar *topic, MatrixRoomVisibility visibility, JsonNode *creation_content, MatrixEventState **initial_state, int n_initial_state, gchar **invitees, int n_invitees, Matrix3PidCredential **invite_3pids, int n_invite_3pids, GError **error) +{ + JsonBuilder *builder = json_builder_new(); + JsonNode *root_node; + + json_builder_begin_object(builder); + + if (creation_content != NULL) { + json_builder_set_member_name(builder, "creation_content"); + json_builder_add_value(builder, creation_content); + } + + if (n_initial_state > 0) { + json_builder_set_member_name(builder, "initial_state"); + json_builder_begin_array(builder); + + for (gint i = 0; i < n_initial_state; i++) { + JsonNode *event_node = matrix_event_base_get_json(MATRIX_EVENT_BASE(initial_state[i])); + + json_builder_add_value(builder, event_node); + } + + json_builder_end_array(builder); + } + + if (n_invitees > 0) { + json_builder_set_member_name(builder, "invite"); + json_builder_begin_array(builder); + + for (gint i = 0; i < n_invitees; i++) { + json_builder_add_string_value(builder, invitees[i]); + } + + json_builder_end_array(builder); + } + + if (n_invite_3pids > 0) { + json_builder_set_member_name(builder, "invite_3pid"); + json_builder_begin_array(builder); + + for (gint i = 0; i < n_invite_3pids; i++) { + GError *inner_error = NULL; + JsonNode *node = matrix_json_compact_get_json_node(MATRIX_JSON_COMPACT(invite_3pids[i]), &inner_error); + + if (inner_error == NULL) { + json_builder_add_value(builder, node); + } + // TODO exceptions should be handled here somehow + } + + json_builder_end_array(builder); + } + + if (room_name != NULL) { + json_builder_set_member_name(builder, "name"); + json_builder_add_string_value(builder, room_name); + } + + if (preset != MATRIX_ROOM_PRESET_NONE) { + gchar *preset_str = _matrix_g_enum_to_string(MATRIX_TYPE_ROOM_PRESET, preset, '_'); + + if (preset_str != NULL) { + json_builder_set_member_name(builder, "preset"); + json_builder_add_string_value(builder, preset_str); + g_free(preset_str); + } else { + g_warning("Invalid room preset type"); + } + } + + if (room_alias != NULL) { + json_builder_set_member_name(builder, "room_alias_name"); + json_builder_add_string_value(builder, room_alias); + } + + if (topic != NULL) { + json_builder_set_member_name(builder, "topic"); + json_builder_add_string_value(builder, topic); + } + + if (visibility != MATRIX_ROOM_VISIBILITY_DEFAULT) { + gchar *visibility_str = _matrix_g_enum_to_string(MATRIX_TYPE_ROOM_VISIBILITY, visibility, '_'); + + if (visibility_str != NULL) { + json_builder_set_member_name(builder, "visibility"); + json_builder_add_string_value(builder, visibility_str); + g_free(visibility_str); + } else { + g_warning("Invalid room visibility type"); + } + } + + json_builder_end_object(builder); + root_node = json_builder_get_root(builder); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "POST", "createRoom", NULL, NULL, root_node, NULL, FALSE, error); + + json_node_unref(root_node); + g_object_unref(builder); +} + +static void +matrix_http_api_delete_room_alias(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_alias, GError **error) +{ + gchar *path; + + g_return_if_fail(room_alias != NULL); + + uri_encode(room_alias); + path = g_strconcat("directory/room/", enc_room_alias, NULL); + g_free(enc_room_alias); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "DELETE", path, NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_get_room_id(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_alias, GError **error) +{ + gchar *path; + + g_return_if_fail(room_alias != NULL); + + uri_encode(room_alias); + path = g_strconcat("directory/room/", enc_room_alias, NULL); + g_free(enc_room_alias); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "GET", path, NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_create_room_alias(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, const gchar *room_alias, GError **error) +{ + JsonBuilder *builder; + JsonNode *root_node; + gchar *path; + + g_return_if_fail(room_id != NULL); + g_return_if_fail(room_alias != NULL); + + uri_encode(room_alias); + path = g_strconcat("directory/room/", enc_room_alias, NULL); + g_free(enc_room_alias); + + builder = json_builder_new(); + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "room_id"); + json_builder_add_string_value(builder, room_id); + + json_builder_end_object(builder); + + root_node = json_builder_get_root(builder); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "PUT", path, NULL, NULL, root_node, NULL, FALSE, error); + + json_node_unref(root_node); + g_object_unref(builder); +} + +static void +matrix_http_api_list_public_rooms(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, GError **error) +{ + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "GET", "publicRooms", NULL, NULL, NULL, NULL, FALSE, error); +} + +static void +matrix_http_api_ban_user(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, const gchar *user_id, const gchar *reason, GError **error) +{ + gchar *path; + JsonNode *root_node; + JsonBuilder *builder; + + g_return_if_fail(room_id != NULL); + g_return_if_fail(user_id != NULL); + + uri_encode(room_id); + path = g_strconcat("rooms/", enc_room_id, "/ban", NULL); + g_free(enc_room_id); + + builder = json_builder_new(); + + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "user_id"); + json_builder_add_string_value(builder, user_id); + + if (reason != NULL) { + json_builder_set_member_name(builder, "reason"); + json_builder_add_string_value(builder, reason); + } + + json_builder_end_object(builder); + root_node = json_builder_get_root(builder); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "POST", path, NULL, NULL, root_node, NULL, FALSE, error); + + g_free(path); + json_node_unref(root_node); + g_object_unref(builder); +} + +static void +matrix_http_api_unban_user(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, const gchar *user_id, GError **error) +{ + gchar *path; + JsonBuilder *builder; + JsonNode *root_node; + + g_return_if_fail(room_id != NULL); + g_return_if_fail(user_id != NULL); + + uri_encode(room_id); + path = g_strconcat("rooms/", enc_room_id, "/unban", NULL); + g_free(enc_room_id); + + builder = json_builder_new(); + + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "user_id"); + json_builder_add_string_value(builder, user_id); + + json_builder_end_object(builder); + root_node = json_builder_get_root(builder); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "POST", path, NULL, NULL, root_node, NULL, FALSE, error); + + g_free(path); + json_node_unref(root_node); + g_object_unref(builder); +} + +static void +matrix_http_api_forget_room(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, GError **error) +{ + gchar *path; + + g_return_if_fail(room_id != NULL); + + uri_encode(room_id); + path = g_strconcat("rooms/", enc_room_id, "/forget", NULL); + g_free(enc_room_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "POST", path, NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_invite_user_3rdparty(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, Matrix3PidCredential *credential, GError **error) +{ + gchar *path; + JsonNode *body; + + g_return_if_fail(room_id != NULL); + g_return_if_fail(credential != NULL); + + uri_encode(room_id); + path = g_strconcat("rooms/", enc_room_id, "/invite", NULL); + g_free(enc_room_id); + + if ((body = matrix_json_compact_get_json_node(MATRIX_JSON_COMPACT(credential), error)) == NULL) { + return; + } + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "POST", path, NULL, NULL, body, NULL, FALSE, error); + + g_free(path); + json_node_unref(body); +} + +static void +matrix_http_api_invite_user(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, const gchar *user_id, GError **error) +{ + gchar *path; + JsonBuilder *builder; + JsonNode *root_node; + + g_return_if_fail(room_id != NULL); + g_return_if_fail(user_id != NULL); + + uri_encode(room_id); + path = g_strconcat("rooms/", enc_room_id, "/invite", NULL); + g_free(enc_room_id); + + builder = json_builder_new(); + + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "user_id"); + json_builder_add_string_value(builder, user_id); + + json_builder_end_object(builder); + root_node = json_builder_get_root(builder); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "POST", path, NULL, NULL, root_node, NULL, FALSE, error); + + g_free(path); + json_node_unref(root_node); + g_object_unref(builder); +} + +static void +matrix_http_api_join_room(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, GError **error) +{ + gchar *path; + + g_return_if_fail(room_id != NULL); + + uri_encode(room_id); + path = g_strconcat("rooms/", enc_room_id, "/join", NULL); + g_free(enc_room_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "POST", path, NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_leave_room(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, GError **error) +{ + gchar *path; + + g_return_if_fail(room_id != NULL); + + uri_encode(room_id); + path = g_strconcat("rooms/", enc_room_id, "/leave", NULL); + g_free(enc_room_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "POST", path, NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_join_room_id_or_alias(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id_or_alias, GError **error) +{ + gchar *path; + + g_return_if_fail(room_id_or_alias != NULL); + + uri_encode(room_id_or_alias); + path = g_strconcat("join/", enc_room_id_or_alias, NULL); + g_free(enc_room_id_or_alias); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "POST", path, NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_kick_user(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, const gchar *user_id, const gchar *reason, GError **error) +{ + gchar *path; + JsonBuilder *builder; + JsonNode *root_node; + + g_return_if_fail(room_id != NULL); + g_return_if_fail(user_id != NULL); + + uri_encode(room_id); + path = g_strconcat("rooms/", enc_room_id, "/kick", NULL); + g_free(enc_room_id); + + builder = json_builder_new(); + + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "user_id"); + json_builder_add_string_value(builder, user_id); + + if (reason != NULL) { + json_builder_set_member_name(builder, "reason"); + json_builder_add_string_value(builder, reason); + } + + json_builder_end_object(builder); + root_node = json_builder_get_root(builder); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "POST", path, NULL, NULL, root_node, NULL, FALSE, error); + + g_free(path); + json_node_unref(root_node); + g_object_unref(builder); +} + +static void +matrix_http_api_event_stream(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *from_token, gulong timeout, GError **error) +{ + GHashTable *parms = _matrix_http_api_create_query_params(); + + if (from_token != NULL) { + g_hash_table_replace(parms, g_strdup("from"), g_strdup(from_token)); + } + + if (timeout > 0) { + g_hash_table_replace(parms, g_strdup("timeout"), g_strdup_printf("%lu", timeout)); + } + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "GET", "events", parms, NULL, NULL, NULL, FALSE, error); + + g_hash_table_unref(parms); +} + +static void +matrix_http_api_get_event(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *event_id, GError **error) +{ + gchar *path; + + g_return_if_fail(event_id != NULL); + + uri_encode(event_id); + path = g_strconcat("events/", enc_event_id, NULL); + g_free(enc_event_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), cb, cb_target, CALL_TYPE_API, "GET", path, NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_initial_sync(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, guint limit, gboolean archived, GError **error) +{ + GHashTable *parms = _matrix_http_api_create_query_params(); + + if (limit != 0) { + g_hash_table_replace(parms, g_strdup("limit"), g_strdup_printf("%u", limit)); + } + + if (archived) { + g_hash_table_replace(parms, g_strdup("archived"), g_strdup("true")); + } + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", "initialSync", + parms, NULL, NULL, NULL, FALSE, error); + + g_hash_table_unref(parms); +} + +static void +matrix_http_api_get_event_context(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, const gchar *event_id, guint limit, GError **error) +{ + gchar *path; + GHashTable *parms = NULL; + + g_return_if_fail(room_id != NULL); + g_return_if_fail(event_id != NULL); + + uri_encode(room_id); + uri_encode(event_id); + path = g_strconcat("rooms/", enc_room_id, "/context", enc_event_id, NULL); + g_free(enc_room_id); + g_free(enc_event_id); + + if (limit > 0) { + parms = _matrix_http_api_create_query_params(); + + g_hash_table_replace(parms, g_strdup("limit"), g_strdup_printf("%u", limit)); + } + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", path, + parms, NULL, NULL, NULL, FALSE, error); + + g_free(path); + g_hash_table_unref(parms); +} + +static void +matrix_http_api_initial_sync_room(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, GError **error) +{ + gchar *path; + + g_return_if_fail(room_id != NULL); + + uri_encode(room_id); + path = g_strconcat("rooms/", enc_room_id, "/initialSync", NULL); + g_free(enc_room_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", path, + NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_list_room_members(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, GError **error) +{ + gchar *path; + + g_return_if_fail(room_id != NULL); + + uri_encode(room_id); + path = g_strconcat("rooms/", enc_room_id, "/members", NULL); + g_free(enc_room_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", path, + NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_list_room_messages(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, const gchar *from_token, MatrixEventDirection direction, guint limit, GError **error) +{ + gchar *path; + GHashTable *parms; + + g_return_if_fail(room_id != NULL); + g_return_if_fail(from_token != NULL); + + uri_encode(room_id); + path = g_strconcat("rooms/", enc_room_id, "/messages", NULL); + g_free(enc_room_id); + + parms = _matrix_http_api_create_query_params(); + + g_hash_table_replace(parms, g_strdup("from"), g_strdup(from_token)); + + switch (direction) { + case MATRIX_EVENT_DIRECTION_BACKWARD: + g_hash_table_replace(parms, g_strdup("dir"), g_strdup("b")); + + break; + case MATRIX_EVENT_DIRECTION_FORWARD: + g_hash_table_replace(parms, g_strdup("dir"), g_strdup("f")); + + break; + } + + if (limit > 0) { + g_hash_table_replace(parms, g_strdup("limit"), g_strdup_printf("%u", limit)); + } + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", path, + parms, NULL, NULL, NULL, FALSE, error); + + g_free(path); + g_hash_table_unref(parms); +} + +static void +matrix_http_api_send_event_receipt(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, MatrixReceiptType receipt_type, const gchar *event_id, JsonNode *receipt, GError **error) +{ + gchar *path; + gchar *type_str; + + g_return_if_fail(room_id != NULL); + g_return_if_fail(event_id != NULL); + g_return_if_fail(receipt != NULL); + + uri_encode(room_id); + type_str = _matrix_g_enum_to_string(MATRIX_TYPE_RECEIPT_TYPE, receipt_type, '_'); + uri_encode(type_str); + g_free(type_str); + uri_encode(event_id); + path = g_strconcat("rooms/", enc_room_id, "/receipt/", enc_type_str, "/", enc_event_id, NULL); + g_free(enc_room_id); + g_free(enc_type_str); + g_free(enc_event_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "POST", path, + NULL, NULL, receipt, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_redact_event(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, const gchar *event_id, const gchar *txn_id, const gchar *reason, GError **error) +{ + gchar *path; + JsonNode *body = NULL; + + g_return_if_fail(room_id != NULL); + g_return_if_fail(event_id != NULL); + g_return_if_fail(txn_id != NULL); + + uri_encode(room_id); + uri_encode(event_id); + uri_encode(txn_id); + path = g_strconcat("rooms/", enc_room_id, "/redact/", enc_event_id, "/", enc_txn_id, NULL); + g_free(enc_room_id); + g_free(enc_event_id); + g_free(enc_txn_id); + + if (reason != NULL) { + JsonBuilder *builder = json_builder_new(); + + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "reason"); + json_builder_add_string_value(builder, reason); + + json_builder_end_object(builder); + body = json_builder_get_root(builder); + } + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "PUT", path, + NULL, NULL, body, NULL, FALSE, error); + + g_free(path); + json_node_unref(body); +} + +static void +matrix_http_api_send_event(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, const gchar *event_type, const gchar *txn_id, JsonNode *content, GError **error) +{ + gchar *path; + + g_return_if_fail(room_id != NULL); + g_return_if_fail(event_type != NULL); + g_return_if_fail(txn_id != NULL); + g_return_if_fail(content != NULL); + + uri_encode(room_id); + uri_encode(event_type); + uri_encode(txn_id); + path = g_strconcat("rooms/", enc_room_id, "/send/", enc_event_type, "/", enc_txn_id, NULL); + g_free(enc_room_id); + g_free(enc_event_type); + g_free(enc_txn_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "PUT", path, + NULL, NULL, content, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_get_room_state(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, const gchar *event_type, const gchar *state_key, GError **error) +{ + gchar *path; + + g_return_if_fail(room_id != NULL); + + uri_encode(room_id); + path = g_strconcat("rooms/", enc_room_id, "/state", NULL); + g_free(enc_room_id); + + if (event_type != NULL) { + gchar *tmp = path; + + uri_encode(event_type); + path = g_strconcat(tmp, "/", enc_event_type, NULL); + g_free(enc_event_type); + g_free(tmp); + } + + if (state_key != NULL) { + gchar *tmp = path; + + uri_encode(state_key); + path = g_strconcat(tmp, "/", enc_state_key, NULL); + g_free(enc_state_key); + g_free(tmp); + } + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", path, + NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_send_state_event(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *room_id, const gchar *event_type, const gchar *state_key, JsonNode *content, GError **error) +{ + gchar *path; + + g_return_if_fail(room_id != NULL); + g_return_if_fail(event_type != NULL); + g_return_if_fail(content != NULL); + + uri_encode(room_id); + uri_encode(event_type); + path = g_strconcat("rooms/", enc_room_id, "/state/", enc_event_type, NULL); + g_free(enc_room_id); + g_free(enc_event_type); + + if (state_key != NULL) { + gchar *tmp = path; + + uri_encode(state_key); + path = g_strconcat(tmp, "/", enc_state_key, NULL); + g_free(enc_state_key); + g_free(tmp); + } + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "PUT", path, + NULL, NULL, content, NULL, FALSE, error); +} + +static void +matrix_http_api_notify_room_typing(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, const gchar *room_id, guint timeout, gboolean typing, GError **error) +{ + gchar *path; + JsonBuilder *builder; + JsonNode *root_node; + + g_return_if_fail(user_id != NULL); + g_return_if_fail(room_id != NULL); + + uri_encode(room_id); + uri_encode(user_id); + + path = g_strconcat("rooms/", enc_room_id, "/typing/", enc_user_id, NULL); + + g_free(enc_room_id); + g_free(enc_user_id); + + builder = json_builder_new(); + + json_builder_begin_object(builder); + + if (timeout > 0) { + json_builder_set_member_name(builder, "timeout"); + json_builder_add_int_value(builder, timeout); + } + + json_builder_set_member_name(builder, "typing"); + json_builder_add_boolean_value(builder, typing); + + json_builder_end_object(builder); + root_node = json_builder_get_root(builder); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "PUT", path, + NULL, NULL, root_node, NULL, FALSE, error); + + g_free(path); + json_node_unref(root_node); + g_object_unref(builder); +} + +static void +matrix_http_api_sync(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *filter_id, MatrixFilter *filter, const gchar *since, gboolean full_state, gboolean set_presence, gulong timeout, GError **error) +{ + GHashTable *parms; + + if ((filter_id != NULL) && (filter != NULL)) { + g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_BAD_REQUEST, + "Cannot set both filter_id and filter"); + + return; + } + + parms = _matrix_http_api_create_query_params(); + + if (filter_id != NULL) { + g_hash_table_replace(parms, g_strdup("filter"), g_strdup(filter_id)); + } + + if (filter != NULL) { + GError *inner_error = NULL; + gchar *filter_data = matrix_json_compact_get_json_data(MATRIX_JSON_COMPACT(filter), NULL, &inner_error); + + if (inner_error != NULL) { + g_propagate_error(error, inner_error); + g_hash_table_unref(parms); + + return; + } + + g_hash_table_replace(parms, g_strdup("filter"), filter_data); + } + + if (since != NULL) { + g_hash_table_replace(parms, g_strdup("since"), g_strdup(since)); + } + + g_hash_table_replace(parms, g_strdup("full_state"), g_strdup((full_state) ? "true" : "false")); + + if (!set_presence) { + g_hash_table_replace(parms, g_strdup("set_presence"), g_strdup("offline")); + } + + if (timeout != 0) { + g_hash_table_replace(parms, g_strdup("timeout"), g_strdup_printf("%lu", timeout)); + } + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", "sync", + parms, NULL, NULL, NULL, FALSE, error); + + g_hash_table_unref(parms); +} + +static void +matrix_http_api_create_filter(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, MatrixFilter *filter, GError **error) +{ + gchar *path; + JsonNode *filter_node; + GError *inner_error = NULL; + + g_return_if_fail(user_id != NULL); + g_return_if_fail(filter != NULL); + + filter_node = matrix_json_compact_get_json_node(MATRIX_JSON_COMPACT(filter), &inner_error); + + if (inner_error != NULL) { + g_propagate_error(error, inner_error); + + return; + } + + uri_encode(user_id); + path = g_strconcat("user/", enc_user_id, "/filter", NULL); + g_free(enc_user_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "POST", path, + NULL, NULL, filter_node, NULL, FALSE, error); + + json_node_unref(filter_node); + g_free(path); +} + +static void +matrix_http_api_download_filter(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, const gchar *filter_id, GError **error) +{ + gchar *path; + + g_return_if_fail(user_id != NULL); + g_return_if_fail(filter_id != NULL); + + uri_encode(user_id); + uri_encode(filter_id); + path = g_strconcat("user/", enc_user_id, "/filter/", enc_filter_id, NULL); + g_free(enc_user_id); + g_free(enc_filter_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", path, + NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_search(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *next_batch, MatrixSearchCategories *search_categories, GError **error) +{ + GHashTable *parms = NULL; + JsonNode *search_node; + GError *inner_error = NULL; + + g_return_if_fail(search_categories != NULL); + + search_node = matrix_json_compact_get_json_node(MATRIX_JSON_COMPACT(search_categories), &inner_error); + + if (inner_error != NULL) { + g_propagate_error(error, inner_error); + + return; + } + + if (next_batch != NULL) { + parms = _matrix_http_api_create_query_params(); + + g_hash_table_replace(parms, g_strdup("next_batch"), g_strdup(next_batch)); + } + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "POST", "search", + parms, NULL, search_node, NULL, FALSE, error); + + json_node_unref(search_node); + g_hash_table_unref(parms); +} + +static void +matrix_http_api_whois(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, GError **error) +{ + gchar *path; + + g_return_if_fail(user_id != NULL); + + uri_encode(user_id); + path = g_strconcat("admin/whois/", enc_user_id, NULL); + g_free(enc_user_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", path, + NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_versions(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, GError **error) +{ + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", "versions", + NULL, NULL, NULL, NULL, FALSE, error); +} + +static void +matrix_http_api_login(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *login_type, JsonNode *content, GError **error) +{ + JsonNode *body; + JsonObject *root; + + g_return_if_fail(login_type != NULL); + + body = _matrix_json_node_deep_copy(content); + root = json_node_get_object(body); + + json_object_set_string_member(root, "type", login_type); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "POST", "login", + NULL, NULL, body, NULL, FALSE, error); + + json_node_unref(body); +} + +static void +matrix_http_api_token_refresh(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *refresh_token, GError **error) +{ + JsonBuilder *builder; + MatrixHTTPAPIPrivate *priv = matrix_http_api_get_instance_private(MATRIX_HTTP_API(matrix_api)); + JsonNode *root_node; + + if ((refresh_token == NULL) && (priv->refresh_token == NULL)) { + g_set_error(error, MATRIX_ERROR, MATRIX_ERROR_M_MISSING_TOKEN, "No token available"); + + return; + } + + builder = json_builder_new(); + + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "refresh_token"); + json_builder_add_string_value(builder, (refresh_token != NULL) ? refresh_token : priv->refresh_token); + + json_builder_end_object(builder); + root_node = json_builder_get_root(builder); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "POST", "tokenrefresh", + NULL, NULL, root_node, NULL, FALSE, error); + + json_node_unref(root_node); + g_object_unref(builder); +} + +static void +matrix_http_api_logout(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, GError **error) +{ + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "POST", "logout", + NULL, NULL, NULL, NULL, FALSE, error); +} + +static void +matrix_http_api_get_3pids(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, GError **error) +{ + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", "account/3pid", + NULL, NULL, NULL, NULL, FALSE, error); +} + +static void +matrix_http_api_add_3pid(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, gboolean bind_creds, Matrix3PidCredential *creds, GError **error) +{ + JsonNode *creds_node; + JsonNode *root_node; + JsonBuilder *builder; + GError *inner_error = NULL; + + g_return_if_fail(creds != NULL); + + creds_node = matrix_json_compact_get_json_node(MATRIX_JSON_COMPACT(creds), &inner_error); + + if (inner_error != NULL) { + g_propagate_error(error, inner_error); + + return; + } + + builder = json_builder_new(); + + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "bind"); + json_builder_add_boolean_value(builder, bind_creds); + + json_builder_set_member_name(builder, "threePidCreds"); + json_builder_add_value(builder, creds_node); + + json_builder_end_object(builder); + + root_node = json_builder_get_root(builder); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "POST", "account/3pid", + NULL, NULL, root_node, NULL, FALSE, error); + + json_node_unref(root_node); + g_object_unref(builder); +} + +static void +matrix_http_api_change_password(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *new_password, GError **error) +{ + JsonBuilder *builder; + JsonNode *root_node; + + g_return_if_fail(new_password != NULL); + + builder = json_builder_new(); + + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "new_password"); + json_builder_add_string_value(builder, new_password); + + json_builder_end_object(builder); + root_node = json_builder_get_root(builder); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "POST", "account/password", + NULL, NULL, root_node, NULL, FALSE, error); + + json_node_unref(root_node); + g_object_unref(builder); +} + +static void +matrix_http_api_get_profile(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, GError **error) +{ + gchar *path; + + g_return_if_fail(user_id != NULL); + + uri_encode(user_id); + path = g_strconcat("profile/", enc_user_id, NULL); + g_free(enc_user_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", path, + NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_get_avatar_url(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, GError **error) +{ + gchar *path; + + g_return_if_fail(user_id != NULL); + + uri_encode(user_id); + path = g_strconcat("profile/", enc_user_id, "/avatar_url", NULL); + g_free(enc_user_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", path, + NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_set_avatar_url(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, const gchar *avatar_url, GError **error) +{ + gchar *path; + JsonBuilder *builder; + JsonNode *root_node; + + g_return_if_fail(user_id != NULL); + g_return_if_fail(avatar_url != NULL); + + uri_encode(user_id); + path = g_strconcat("profile/", enc_user_id, "/avatar_url", NULL); + g_free(enc_user_id); + + builder = json_builder_new(); + + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "avatar_url"); + json_builder_add_string_value(builder, avatar_url); + + json_builder_end_object(builder); + + root_node = json_builder_get_root(builder); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "PUT", path, + NULL, NULL, root_node, NULL, FALSE, error); + + json_node_unref(root_node); + g_object_unref(builder); + g_free(path); +} + +static void +matrix_http_api_get_display_name(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, GError **error) +{ + gchar *path; + + g_return_if_fail(user_id != NULL); + + uri_encode(user_id); + path = g_strconcat("profile/", enc_user_id, "/displayname", NULL); + g_free(enc_user_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", path, + NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_set_display_name(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_name, const gchar *display_name, GError **error) +{ + gchar *path; + JsonBuilder *builder; + JsonNode *root_node; + + g_return_if_fail(user_name != NULL); + g_return_if_fail(display_name != NULL); + + uri_encode(user_name); + path = g_strconcat("profile/", enc_user_name, "/displayname", NULL); + g_free(enc_user_name); + + builder = json_builder_new(); + + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "displayname"); + json_builder_add_string_value(builder, display_name); + + json_builder_end_object(builder); + root_node = json_builder_get_root(builder); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "PUT", path, + NULL, NULL, root_node, NULL, FALSE, error); + + json_node_unref(root_node); + g_object_unref(builder); + g_free(path); +} + +static void +matrix_http_api_register_account(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, MatrixAccountKind account_kind, gboolean bind_email, const gchar *username, const gchar *password, GError **error) +{ + GHashTable *parms = NULL; + JsonBuilder *builder; + JsonNode *root_node; + + g_return_if_fail(password != NULL); + + builder = json_builder_new(); + + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "bind_email"); + json_builder_add_boolean_value(builder, bind_email); + + if (username != NULL) { + json_builder_set_member_name(builder, "username"); + json_builder_add_string_value(builder, username); + } + + json_builder_set_member_name(builder, "password"); + json_builder_add_string_value(builder, password); + + json_builder_end_object(builder); + root_node = json_builder_get_root(builder); + + if (account_kind != MATRIX_ACCOUNT_KIND_DEFAULT) { + gchar *account_kind_str = _matrix_g_enum_to_string(MATRIX_TYPE_ACCOUNT_KIND, account_kind, '_'); + + if (account_kind_str != NULL) { + parms = _matrix_http_api_create_query_params(); + + g_hash_table_replace(parms, g_strdup("kind"), account_kind_str); + } + } + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "POST", "register", + parms, NULL, root_node, NULL, FALSE, error); + + json_node_unref(root_node); + g_object_unref(builder); + g_hash_table_unref(parms); +} + +static void +matrix_http_api_set_account_data(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, const gchar *room_id, const gchar *event_type, JsonNode *content, GError **error) +{ + gchar *path; + + g_return_if_fail(user_id != NULL); + g_return_if_fail(event_type != NULL); + g_return_if_fail(content != NULL); + + uri_encode(user_id); + uri_encode(event_type); + + if (room_id != NULL) { + uri_encode(room_id); + path = g_strconcat("user/", enc_user_id, "/rooms/", enc_room_id, "/account_data/", enc_event_type, NULL); + g_free(enc_room_id); + } else { + path = g_strconcat("user/", enc_user_id, "/account_data/", enc_event_type, NULL); + } + + g_free(enc_user_id); + g_free(enc_event_type); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "PUT", path, + NULL, NULL, content, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_get_room_tags(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, const gchar *room_id, GError **error) +{ + gchar *path; + + g_return_if_fail(user_id != NULL); + g_return_if_fail(room_id != NULL); + + uri_encode(user_id); + uri_encode(room_id); + path = g_strconcat("user/", enc_user_id, "/rooms/", enc_room_id, "/tags", NULL); + g_free(enc_user_id); + g_free(enc_room_id); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", path, + NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_delete_room_tag(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, const gchar *room_id, const gchar *tag, GError **error) +{ + gchar *path; + + g_return_if_fail(user_id != NULL); + g_return_if_fail(room_id != NULL); + g_return_if_fail(tag != NULL); + + uri_encode(user_id); + uri_encode(room_id); + uri_encode(tag); + path = g_strconcat("user/", enc_user_id, "/rooms/", enc_room_id, "/tags/", enc_tag, NULL); + g_free(enc_user_id); + g_free(enc_room_id); + g_free(enc_tag); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "DELETE", path, + NULL, NULL, NULL, NULL, FALSE, error); + + g_free(path); +} + +static void +matrix_http_api_add_room_tag(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *user_id, const gchar *room_id, const gchar *tag, JsonNode *content, GError **error) +{ + gchar *path; + + g_return_if_fail(user_id != NULL); + g_return_if_fail(room_id != NULL); + g_return_if_fail(tag != NULL); + + uri_encode(user_id); + uri_encode(room_id); + uri_encode(tag); + + path = g_strconcat("user/", enc_user_id, "/rooms/", enc_room_id, "/tags/", enc_tag, NULL); + + g_free(enc_user_id); + g_free(enc_room_id); + g_free(enc_tag); + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "PUT", path, + NULL, NULL, content, NULL, FALSE, error); +} + +static void +matrix_http_api_deactivate_account(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, const gchar *session, const gchar *login_type, GError **error) +{ + JsonNode *root_node = NULL; + + if (login_type != NULL) { + JsonBuilder *builder = json_builder_new(); + + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "auth"); + json_builder_begin_object(builder); + + if (session != NULL) { + json_builder_set_member_name(builder, "session"); + json_builder_add_string_value(builder, session); + } + + json_builder_set_member_name(builder, "type"); + json_builder_add_string_value(builder, login_type); + + json_builder_end_object(builder); + json_builder_end_object(builder); + + root_node = json_builder_get_root(builder); + + g_object_unref(builder); + } + + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "POST", "account/deactivate", + NULL, NULL, root_node, NULL, FALSE, error); + + json_node_unref(root_node); +} + +static void +matrix_http_api_get_turn_server(MatrixAPI *matrix_api, MatrixAPICallback cb, void *cb_target, GError **error) +{ + _matrix_http_api_send(MATRIX_HTTP_API(matrix_api), + cb, cb_target, + CALL_TYPE_API, "GET", "voip/turnServer", + NULL, NULL, NULL, NULL, FALSE, error); +} + +static void +matrix_http_api_abort_pending (MatrixAPI *matrix_api) +{ + MatrixHTTPAPIPrivate *priv = matrix_http_api_get_instance_private(MATRIX_HTTP_API(matrix_api)); + + soup_session_abort(priv->soup_session); +} + +const gchar * +matrix_http_api_get_base_url (MatrixHTTPAPI *matrix_http_api) +{ + MatrixHTTPAPIPrivate *priv; + + g_return_val_if_fail(matrix_http_api != NULL, NULL); + + priv = matrix_http_api_get_instance_private(matrix_http_api); + + return priv->base_url; +} + +static gint +string_last_index_of(const gchar *str, const gchar *needle, gint start_index) +{ + gchar *occurence = NULL; + + g_return_val_if_fail(str != NULL, -1); + g_return_val_if_fail(needle != NULL, -1); + + if ((occurence = g_strrstr (str + start_index, needle)) != NULL){ + return (gint)(occurence - ((gchar*)str)); + } + + return -1; +} + +void +matrix_http_api_set_base_url(MatrixHTTPAPI *matrix_http_api, const gchar *base_url) +{ + MatrixHTTPAPIPrivate *priv; + SoupURI *api_uri; + SoupURI *media_uri; + + g_return_if_fail(matrix_http_api != NULL); + + priv = matrix_http_api_get_instance_private(matrix_http_api); + + if (!g_str_is_ascii(base_url)) { + g_warning("URL specified(%s) is not ASCII", base_url); + + return; + } + + if (string_last_index_of(base_url, API_ENDPOINT, 0) != -1) { + g_warning("Provided URL (%s) already contains the API endpoint. Please use an URL without it!", + base_url); + + return; + } + + _matrix_http_api_set_url(matrix_http_api, &api_uri, base_url, API_ENDPOINT); + _matrix_http_api_set_url(matrix_http_api, &media_uri, base_url, MEDIA_ENDPOINT); + + if ((api_uri != NULL) && (media_uri != NULL)) { + if (priv->api_uri != NULL) { + soup_uri_free(priv->api_uri); + } + + if (priv->media_uri != NULL) { + soup_uri_free(priv->media_uri); + } + + g_free(priv->base_url); + + priv->api_uri = api_uri; + priv->media_uri = media_uri; + priv->base_url = g_strdup(base_url); + + g_free(priv->token); + g_free(priv->refresh_token); + g_free(matrix_http_api->_homeserver); + g_free(matrix_http_api->_user_id); + priv->token = NULL; + priv->refresh_token = NULL; + matrix_http_api->_homeserver = NULL; + matrix_http_api->_user_id = NULL; + +#if DEBUG + gchar *uri; + + uri = soup_uri_to_string(api_uri, FALSE); + g_debug("API URL: %s", uri); + g_free(uri); + + uri = soup_uri_to_string(media_uri, FALSE); + g_debug("Media URL: %s", uri); + g_free(uri); +#endif + } else { + g_warning("Invalid base URL: %s", base_url); + } + + g_object_notify_by_pspec((GObject *)matrix_http_api, matrix_http_api_properties[PROP_BASE_URL]); +} + +gboolean +matrix_http_api_get_validate_certificate(MatrixHTTPAPI *matrix_http_api) +{ + MatrixHTTPAPIPrivate *priv; + gboolean result; + + g_return_val_if_fail(matrix_http_api != NULL, FALSE); + + priv = matrix_http_api_get_instance_private(matrix_http_api); + + g_object_get(priv->soup_session, "ssl-strict", &result, NULL); + + return result; +} + +void +matrix_http_api_set_validate_certificate(MatrixHTTPAPI *matrix_http_api, gboolean validate_certificate) +{ + MatrixHTTPAPIPrivate *priv; + + g_return_if_fail(matrix_http_api != NULL); + + priv = matrix_http_api_get_instance_private(matrix_http_api); + + g_object_set(priv->soup_session, "ssl-strict", validate_certificate, NULL); + + g_object_notify_by_pspec ((GObject *) matrix_http_api, matrix_http_api_properties[PROP_VALIDATE_CERTIFICATE]); +} + +static const gchar * +matrix_http_api_get_user_id (MatrixAPI *api) +{ + return MATRIX_HTTP_API(matrix_api)->_user_id; +} + +static const gchar * +matrix_http_api_get_token(MatrixAPI *matrix_api) +{ + MatrixHTTPAPIPrivate *priv = matrix_http_api_get_instance_private(MATRIX_HTTP_API(matrix_api)); + + return priv->token; +} + +static void +matrix_http_api_set_token(MatrixAPI *matrix_api, const gchar *token) +{ + MatrixHTTPAPIPrivate *priv = matrix_http_api_get_instance_private(MATRIX_HTTP_API(matrix_api)); + + if (g_strcmp0(token, priv->token) != 0) { + g_free(priv->token); + priv->token = g_strdup(token); + + g_object_notify_by_pspec((GObject *)matrix_api, matrix_http_api_properties[PROP_TOKEN]); + } +} + +static const gchar * +matrix_http_api_get_refresh_token (MatrixAPI *matrix_api) +{ + MatrixHTTPAPIPrivate *priv = matrix_http_api_get_instance_private(MATRIX_HTTP_API(matrix_api)); + + return priv->refresh_token; +} + +static void +matrix_http_api_set_refresh_token(MatrixAPI *matrix_api, const gchar *refresh_token) +{ + MatrixHTTPAPIPrivate *priv = matrix_http_api_get_instance_private(MATRIX_HTTP_API(matrix_api)); + + if (g_strcmp0(refresh_token, priv->refresh_token) != 0) { + g_free(priv->refresh_token); + priv->refresh_token = g_strdup(refresh_token); + + g_object_notify_by_pspec((GObject *)matrix_api, matrix_http_api_properties[PROP_REFRESH_TOKEN]); + } +} + +static const gchar * +matrix_http_api_get_homeserver(MatrixAPI *api) { + return MATRIX_HTTP_API(api)->_homeserver; +} + +static void +matrix_http_api_finalize(GObject *gobject) +{ + MatrixHTTPAPI *matrix_http_api = MATRIX_HTTP_API(gobject); + MatrixHTTPAPIPrivate *priv = matrix_http_api_get_instance_private(MATRIX_HTTP_API(gobject)); + + g_object_unref(priv->soup_session); + g_free(priv->base_url); + + if (priv->api_uri != NULL) { + soup_uri_free(priv->api_uri); + } + + if (priv->media_uri != NULL) { + soup_uri_free(priv->media_uri); + } + + g_free(matrix_http_api->_user_id); + g_free(priv->token); + g_free(priv->refresh_token); + g_free(matrix_http_api->_homeserver); + + G_OBJECT_CLASS(matrix_http_api_parent_class)->finalize(gobject); +} + +static void +matrix_http_api_get_property(GObject *gobject, guint property_id, GValue *value, GParamSpec *pspec) +{ + MatrixHTTPAPI *matrix_http_api = MATRIX_HTTP_API(gobject); + + switch (property_id) { + case PROP_BASE_URL: + g_value_set_string(value, matrix_http_api_get_base_url(matrix_http_api)); + + break; + case PROP_VALIDATE_CERTIFICATE: + g_value_set_boolean(value, matrix_http_api_get_validate_certificate(matrix_http_api)); + + break; + case PROP_USER_ID: + g_value_set_string(value, matrix_api_get_user_id((MatrixAPI*) matrix_http_api)); + + break; + case PROP_TOKEN: + g_value_set_string(value, matrix_api_get_token((MatrixAPI*) matrix_http_api)); + + break; + case PROP_REFRESH_TOKEN: + g_value_set_string(value, matrix_api_get_refresh_token((MatrixAPI*) matrix_http_api)); + + break; + case PROP_HOMESERVER: + g_value_set_string(value, matrix_api_get_homeserver((MatrixAPI*) matrix_http_api)); + + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, property_id, pspec); + + break; + } +} + +static void +matrix_http_api_set_property(GObject *gobject, guint property_id, const GValue *value, GParamSpec *pspec) +{ + MatrixHTTPAPI * matrix_http_api = MATRIX_HTTP_API(gobject); + + switch(property_id) { + case PROP_BASE_URL: + matrix_http_api_set_base_url(matrix_http_api, g_value_get_string(value)); + + break; + case PROP_VALIDATE_CERTIFICATE: + matrix_http_api_set_validate_certificate(matrix_http_api, g_value_get_boolean(value)); + + break; + case PROP_TOKEN: + matrix_api_set_token((MatrixAPI*) matrix_http_api, g_value_get_string(value)); + + break; + case PROP_REFRESH_TOKEN: + matrix_api_set_refresh_token((MatrixAPI*) matrix_http_api, g_value_get_string(value)); + + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, property_id, pspec); + + break; + } +} + +static void +matrix_http_api_class_init(MatrixHTTPAPIClass *klass) +{ + G_OBJECT_CLASS(klass)->get_property = matrix_http_api_get_property; + G_OBJECT_CLASS(klass)->set_property = matrix_http_api_set_property; + G_OBJECT_CLASS(klass)->finalize = matrix_http_api_finalize; + + /** + * MatrixHTTPAPI:base-url: + * + * The base URL of the API. + */ + matrix_http_api_properties[PROP_BASE_URL] = g_param_spec_string( + "base-url", "base-url", "base-url", + NULL, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); + g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_BASE_URL, matrix_http_api_properties[PROP_BASE_URL]); + + /** + * MatrixHTTPAPI:validate-certificate: + * + * If %TRUE (the default), the certificate will be checked; otherwise the library accepts + * invalid (eg. self-signed) certificates. + */ + matrix_http_api_properties[PROP_VALIDATE_CERTIFICATE] = g_param_spec_boolean( + "validate-certificate", "validate-certificate", "validate-certificate", + FALSE, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); + g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_VALIDATE_CERTIFICATE, matrix_http_api_properties[PROP_VALIDATE_CERTIFICATE]); + + matrix_http_api_properties[PROP_USER_ID] = g_param_spec_string( + "user-id", "user-id", "user-id", + NULL, + G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_USER_ID, matrix_http_api_properties[PROP_USER_ID]); + + matrix_http_api_properties[PROP_TOKEN] = g_param_spec_string( + "token", "token", "token", + NULL, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); + g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_TOKEN, matrix_http_api_properties[PROP_TOKEN]); + + matrix_http_api_properties[PROP_REFRESH_TOKEN] = g_param_spec_string( + "refresh-token", "refresh-token", "refresh-token", + NULL, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); + g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_REFRESH_TOKEN, matrix_http_api_properties[PROP_REFRESH_TOKEN]); + + matrix_http_api_properties[PROP_HOMESERVER] = g_param_spec_string( + "homeserver", "homeserver", "homeserver", + NULL, + G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_HOMESERVER, matrix_http_api_properties[PROP_HOMESERVER]); +} + +static void +matrix_http_api_matrix_api_interface_init(MatrixAPIInterface * iface) +{ + iface->media_download = matrix_http_api_media_download; + iface->media_thumbnail = matrix_http_api_media_thumbnail; + iface->media_upload = matrix_http_api_media_upload; + iface->get_presence_list = matrix_http_api_get_presence_list; + iface->update_presence_list = matrix_http_api_update_presence_list; + iface->get_presence = matrix_http_api_get_presence; + iface->set_presence = matrix_http_api_set_presence; + iface->update_pusher = matrix_http_api_update_pusher; + iface->get_pushers = matrix_http_api_get_pushers; + iface->delete_pushrule = matrix_http_api_delete_pushrule; + iface->get_pushrule = matrix_http_api_get_pushrule; + iface->add_pushrule = matrix_http_api_add_pushrule; + iface->toggle_pushrule = matrix_http_api_toggle_pushrule; + iface->get_pushrules = matrix_http_api_get_pushrules; + iface->create_room = matrix_http_api_create_room; + iface->delete_room_alias = matrix_http_api_delete_room_alias; + iface->get_room_id = matrix_http_api_get_room_id; + iface->create_room_alias = matrix_http_api_create_room_alias; + iface->list_public_rooms = matrix_http_api_list_public_rooms; + iface->ban_user = matrix_http_api_ban_user; + iface->unban_user = matrix_http_api_unban_user; + iface->forget_room = matrix_http_api_forget_room; + iface->invite_user_3rdparty = matrix_http_api_invite_user_3rdparty; + iface->invite_user = matrix_http_api_invite_user; + iface->join_room = matrix_http_api_join_room; + iface->leave_room = matrix_http_api_leave_room; + iface->join_room_id_or_alias = matrix_http_api_join_room_id_or_alias; + iface->kick_user = matrix_http_api_kick_user; + iface->event_stream = matrix_http_api_event_stream; + iface->get_event = matrix_http_api_get_event; + iface->initial_sync = matrix_http_api_initial_sync; + iface->get_event_context = matrix_http_api_get_event_context; + iface->initial_sync_room = matrix_http_api_initial_sync_room; + iface->list_room_members = matrix_http_api_list_room_members; + iface->list_room_messages = matrix_http_api_list_room_messages; + iface->send_event_receipt = matrix_http_api_send_event_receipt; + iface->redact_event = matrix_http_api_redact_event; + iface->send_event = matrix_http_api_send_event; + iface->get_room_state = matrix_http_api_get_room_state; + iface->send_state_event = matrix_http_api_send_state_event; + iface->notify_room_typing = matrix_http_api_notify_room_typing; + iface->sync = matrix_http_api_sync; + iface->create_filter = matrix_http_api_create_filter; + iface->download_filter = matrix_http_api_download_filter; + iface->search = matrix_http_api_search; + iface->whois = matrix_http_api_whois; + iface->versions = matrix_http_api_versions; + iface->login = matrix_http_api_login; + iface->token_refresh = matrix_http_api_token_refresh; + iface->logout = matrix_http_api_logout; + iface->get_3pids = matrix_http_api_get_3pids; + iface->add_3pid = matrix_http_api_add_3pid; + iface->change_password = matrix_http_api_change_password; + iface->get_profile = matrix_http_api_get_profile; + iface->get_avatar_url = matrix_http_api_get_avatar_url; + iface->set_avatar_url = matrix_http_api_set_avatar_url; + iface->get_display_name = matrix_http_api_get_display_name; + iface->set_display_name = matrix_http_api_set_display_name; + iface->register_account = matrix_http_api_register_account; + iface->set_account_data = matrix_http_api_set_account_data; + iface->get_room_tags = matrix_http_api_get_room_tags; + iface->delete_room_tag = matrix_http_api_delete_room_tag; + iface->add_room_tag = matrix_http_api_add_room_tag; + iface->deactivate_account = matrix_http_api_deactivate_account; + iface->get_turn_server = matrix_http_api_get_turn_server; + iface->abort_pending = matrix_http_api_abort_pending; + iface->get_user_id = matrix_http_api_get_user_id; + iface->get_token = matrix_http_api_get_token; + iface->set_token = matrix_http_api_set_token; + iface->get_refresh_token = matrix_http_api_get_refresh_token; + iface->set_refresh_token = matrix_http_api_set_refresh_token; + iface->get_homeserver = matrix_http_api_get_homeserver; +} + +static void +matrix_http_api_init(MatrixHTTPAPI *matrix_http_api) +{ + MatrixHTTPAPIPrivate *priv = matrix_http_api_get_instance_private(matrix_http_api); + + priv->soup_session = soup_session_new(); + priv->base_url = NULL; + priv->api_uri = NULL; + priv->media_uri = NULL; + priv->token = NULL; + priv->refresh_token = NULL; +} diff --git a/src/matrix-http-api.h b/src/matrix-http-api.h new file mode 100644 index 0000000..9d1698e --- /dev/null +++ b/src/matrix-http-api.h @@ -0,0 +1,58 @@ +/* + * This file is part of matrix-glib-sdk + * + * matrix-glib-sdk is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * matrix-glib-sdk is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with matrix-glib-sdk. If not, see + * . + */ + +#ifndef __MATRIX_GLIB_SDK_HTTP_API_H__ +# define __MATRIX_GLIB_SDK_HTTP_API_H__ + +# include +# include "matrix-api.h" + +G_BEGIN_DECLS + +# define MATRIX_TYPE_HTTP_API (matrix_http_api_get_type ()) +# define MATRIX_HTTP_API(o) (G_TYPE_CHECK_INSTANCE_CAST((o), MATRIX_TYPE_HTTP_API, MatrixHTTPAPI)) +# define MATRIX_HTTP_API_CLASS(c) (G_TYPE_CHECK_CLASS_CAST((c), MATRIX_TYPE_HTTP_API, MatrixHTTPAPIClass)) +# define MATRIX_IS_HTTP_API(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), MATRIX_TYPE_HTTP_API)) +# define MATRIX_IS_HTTP_API_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE((c), MATRIX_TYPE_HTTP_API)) +# define MATRIX_HTTP_API_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), MATRIX_TYPE_HTTP_API, MatrixHTTPAPIClass)) + +typedef struct _MatrixHTTPAPI MatrixHTTPAPI; +typedef struct _MatrixHTTPAPIClass MatrixHTTPAPIClass; +G_DEFINE_AUTOPTR_CLEANUP_FUNC(MatrixHTTPAPI, g_object_unref) + +struct _MatrixHTTPAPI { + GObject parent_instance; + + gchar *_homeserver; + gchar *_user_id; +}; + +struct _MatrixHTTPAPIClass { + GObjectClass parent_class; +}; + +GType matrix_http_api_get_type(void) G_GNUC_CONST; +MatrixHTTPAPI *matrix_http_api_new(const gchar *base_url, const gchar *token, const gchar *refresh_token); +const gchar *matrix_http_api_get_base_url(MatrixHTTPAPI *http_api); +void matrix_http_api_set_base_url(MatrixHTTPAPI *http_api, const gchar *base_url); +gboolean matrix_http_api_get_validate_certificate(MatrixHTTPAPI *http_api); +void matrix_http_api_set_validate_certificate(MatrixHTTPAPI *http_api, gboolean validate_certificate); + +G_END_DECLS + +#endif /* __MATRIX_GLIB_SDK_HTTP_API_H__ */ diff --git a/src/matrix-http-api.vala b/src/matrix-http-api.vala deleted file mode 100644 index dd89011..0000000 --- a/src/matrix-http-api.vala +++ /dev/null @@ -1,1913 +0,0 @@ -/* - * This file is part of matrix-glib-sdk - * - * matrix-glib-sdk is free software: you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * matrix-glib-sdk is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with matrix-glib-sdk. If not, see - * . - */ - -/** - * This is a class for low level communication with a Matrix.org - * server via HTTP. - */ -[CCode (lower_case_csuffix = "http_api")] -public class Matrix.HTTPAPI : GLib.Object, Matrix.API { - private enum CallType { - API, - MEDIA; - } - - private const string API_ENDPOINT = "/_matrix/client/r0/"; - private const string MEDIA_ENDPOINT = "/_matrix/media/r0/"; - - private Soup.Session _soup_session = new Soup.Session(); - private string? _base_url = null; - private Soup.URI? _api_uri = null; - private Soup.URI? _media_uri = null; - - public string base_url { - get { - return _base_url; - } - - set { - Soup.URI? api_uri; - Soup.URI? media_uri; - - if (!value.is_ascii()) { - warning("URL specified(%s) is not ASCII", value); - - return; - } - - if (value.last_index_of(API_ENDPOINT) != -1) { - warning("Provided URL (%s) already contains the API endpoint. Please use an URL without it!", - base_url); - - return; - } - - _set_url(out api_uri, value, API_ENDPOINT); - _set_url(out media_uri, value, MEDIA_ENDPOINT); - - if ((api_uri != null) && (media_uri != null)) { - _api_uri = api_uri; - _media_uri = media_uri; - _base_url = value; - - _token = null; - _refresh_token = null; - _homeserver = null; - _user_id = null; - - if (Config.DEBUG) { - debug("API URL: %s", api_uri.to_string(false)); - debug("Media URL: %s", media_uri.to_string(false)); - } - } else { - warning("Invalid base URL: %s", value); - } - } - } - public bool validate_certificate { - get { - return _soup_session.ssl_strict; - } - - set { - _soup_session.ssl_strict = value; - } - } - protected string? _user_id; - public string? user_id { - get { - return _user_id; - } - - default = null; - } - public string? token { get; set; default = null; } - public string? refresh_token { get; set; default = null; } - protected string? _homeserver; - public string? homeserver { - get { - return _homeserver; - } - - default = null; - } - - private void - _set_url(out Soup.URI? uri, string base_url, string endpoint) - { - string url; - - if (base_url[base_url.length - 1] == '/') { - url = "%s%s".printf(base_url, endpoint.offset(1)); - } else { - url = "%s%s".printf(base_url, endpoint); - } - - var new_uri = new Soup.URI(url); - - if (SOUP_URI_VALID_FOR_HTTP(new_uri)) { - uri = new_uri; - } else { - uri = null; - } - } - - /** - * Create a new MatrixHTTPAPI object. - * - * @param base_url the base URL of the homeserver to use - * @param token an access token to use - * @param refresh_token a refresh token to use - * @return a new MatrixHTTPAPI object - */ - protected - HTTPAPI(string base_url, string? token = null, string? refresh_token = null) - { - Object(base_url : base_url, - token : token, - refresh_token : refresh_token); - _soup_session.ssl_strict = true; - } - - // This macro is not available in Vala - private static bool - SOUP_URI_VALID_FOR_HTTP(Soup.URI? uri) - { - return ((uri != null) - && ((uri.scheme == "http") - || (uri.scheme == "https")) - && (uri.host != null) - && (uri.path != null)); - } - - private void - _send(API.Callback? cb, - CallType call_type, - string method, - string path, - owned GLib.HashTable? parms, - string? content_type, - Json.Node? json_content, - ByteArray? raw_content, - bool accept_non_json) - throws Matrix.Error - { - if ((_api_uri == null) || (_media_uri == null)) { - throw new Matrix.Error.COMMUNICATION_ERROR("No valid base URL"); - } - - if ((json_content != null) && (raw_content != null)) { - critical("json_content and raw_content cannot be used together. This is a bug."); - } - - if (!method.is_ascii()) { - critical("Method must be ASCII. This is a bug."); - } - - if ((method.ascii_casecmp("GET") != 0) - && (method.ascii_casecmp("POST") != 0) - && (method.ascii_casecmp("PUT") != 0) - && (method.ascii_casecmp("DELETE") != 0)) { - critical("Method %s is invalid. This is a bug.", method); - } - - Soup.URI? request_path = null; - - if (call_type == CallType.MEDIA) { - request_path = new Soup.URI.with_base(_media_uri, path); - } else { - request_path = new Soup.URI.with_base(_api_uri, path); - } - - if (parms == null) { - parms = _create_query_params(); - } - - if (token != null) { - if (Config.DEBUG) { - debug("Adding access token '%s'", token); - } - - parms.replace("access_token", token); - } - - request_path.set_query_from_form(parms); - - var message = new Soup.Message.from_uri(method, request_path); - - uint8[] request_data; - - if (json_content != null) { - var generator = new Json.Generator(); - generator.set_root(json_content); - var json_str = generator.to_data(null); - request_data = json_str.data; - request_data[request_data.length] = 0; - } else if (raw_content != null) { - request_data = raw_content.data; - } else { - request_data = "{}".data; - request_data[2] = 0; - } - - if (Config.DEBUG) { - debug("Sending %d bytes (%s %s): %s", - request_data.length, - method, - request_path.to_string(false), - (raw_content != null) - ? "" - : (string)request_data); - } - - message.set_flags(Soup.MessageFlags.NO_REDIRECT); - message.set_request( - (content_type == null) - ? "application/json" - : content_type, - Soup.MemoryUse.COPY, - request_data); - - _soup_session.queue_message( - message, - (session, msg) => _response_callback(msg, - call_type, - accept_non_json, - cb)); - } - - private void - _response_callback(Soup.Message msg, - CallType call_type, - bool accept_non_json, - API.Callback? cb) - { - string request_url = msg.get_uri().get_path(); - Matrix.Error? err = null; - ByteArray? raw_content = null; - Json.Node? content = null; - - switch (call_type) { - case CallType.API: - request_url = request_url .substring(API_ENDPOINT.length); - - break; - - case CallType.MEDIA: - request_url = request_url.substring(MEDIA_ENDPOINT.length); - - break; - } - - if ((msg.status_code < 100) - || (msg.status_code >= 400)) { - err = new Matrix.Error.COMMUNICATION_ERROR( - "%s %u: %s", - (msg.status_code < 100) ? "Network error" : "HTTP", - msg.status_code, - msg.reason_phrase); - } else { - var buffer = msg.response_body.flatten(); - string data = (string)buffer.data; - var datalen = buffer.length; - var parser = new Json.Parser(); - bool is_json; - - try { - is_json = parser.load_from_data(data, (ssize_t)datalen); - } catch (GLib.Error e) { - is_json = false; - } - - if (is_json) { - if (Config.DEBUG) { - debug("Response (%s): %s", request_url, data); - } - - content = parser.get_root(); - - if (content.get_node_type() == Json.NodeType.OBJECT) { - var root = content.get_object(); - Json.Node node; - - /* Check if the response holds an access token; if it - * does, set it as our new token */ - if ((node = root.get_member("access_token")) != null) { - string? access_token; - - if ((access_token = node.get_string()) != null) { - if (Config.DEBUG) { - debug("Got new access token: %s", access_token); - } - - token = access_token; - } - } - - /* Check if the response holds a refresh token; if it - * does, set it as our new refresh token */ - if ((node = root.get_member("refresh_token")) != null) { - string? refresh_token; - - if ((refresh_token = node.get_string()) != null) { - if (Config.DEBUG) { - debug("Got new refresh token: %s", - refresh_token); - } - - this.refresh_token = refresh_token; - } - } - - /* Check if the response holds a homeserver name */ - if ((node = root.get_member("home_server")) != null) { - string homeserver = node.get_string(); - - if (Config.DEBUG) { - debug("Our home server calls itself %s", - homeserver); - } - - this._homeserver = homeserver; - } - - /* Check if the response holds a user ID; if it does, - * set this as our user ID */ - if ((node = root.get_member("user_id")) != null) { - string user_id = node.get_string(); - - if (Config.DEBUG) { - debug("We are reported to be logged in as %s", - user_id); - } - - this._user_id = user_id; - } - - /* Check if the response holds an error */ - var errcode_node = root.get_member("errcode"); - var error_node = root.get_member("error"); - string? error = null; - string? errcode = null; - - if ((errcode_node != null) || (error_node != null)) { - err = new Matrix.Error.UNKNOWN_ERROR( - "The error is not known to this library"); - - if (error_node != null) { - error = error_node.get_string(); - } - - if (errcode_node != null) { - errcode = errcode_node.get_string(); - - if (errcode.ascii_ncasecmp("M_", 2) == 0) { - // This is an ugly hack until Vala - // registers errordomains as an - // EnumClass - switch (errcode) { - case "M_MISSING_TOKEN": - err = new Matrix.Error.M_MISSING_TOKEN(""); - - break; - - case "M_FORBIDDEN": - err = new Matrix.Error.M_FORBIDDEN(""); - - break; - - case "M_UNKNOWN": - err = new Matrix.Error.M_UNKNOWN(""); - - break; - - case "M_UNKNOWN_TOKEN": - err = new Matrix.Error.M_UNKNOWN_TOKEN(""); - - break; - - case "M_NOT_JSON": - err = new Matrix.Error.M_NOT_JSON(""); - - break; - - case "M_UNRECOGNIZED": - err = new Matrix.Error.M_UNRECOGNIZED(""); - - break; - - default: - warning("An unknown error code '%s' was sent by the homeserver. You may want to report it to the Matrix GLib developers", errcode); - - break; - } - } - } else { - warning("An error was sent by the homeserver, but no error code was specified. You may want to report this to the homeserver admins."); - err = new Matrix.Error.UNSPECIFIED("No error code was sent by the server"); - } - - if ((errcode_node != null) && (error_node != null)) { - err.message = "%s: %s".printf(errcode, error); - } else if (errcode_node != null) { - err.message = errcode; - } else { - err.message = "(No errcode given) %s".printf(error); - } - } - } else if (content.get_node_type() != Json.NodeType.ARRAY) { - err = new Matrix.Error.BAD_RESPONSE( - "Bad response: not a JSON object, nor an array."); - } - } else { - if (accept_non_json) { - raw_content = new ByteArray.sized((uint)datalen); - raw_content.append(buffer.data); - - if (Config.DEBUG) { - debug("Binary data (%s): %u bytes", - request_url, (uint)datalen); - } - } else { - err = new Matrix.Error.BAD_RESPONSE( - "Malformed response (invalid JSON)"); - - if (Config.DEBUG) { - debug("Malformed response (%s): %s", request_url, data); - } - } - } - } - - /* Call the assigned function, if any */ - if (cb != null) { - cb(this, - msg.response_headers.get_content_type(null), - content, - raw_content, - err); - } - } - - private static HashTable - _create_query_params() - { - return new HashTable(str_hash, str_equal); - } - - /* Media */ - - public void - media_download(API.Callback? cb, - string server_name, - string media_id) - throws Matrix.Error - { - string path = "download/" - + Soup.URI.encode(server_name, null) - + "/" - + Soup.URI.encode(media_id, null); - - _send(cb, - CallType.MEDIA, "GET", path, - null, null, null, null, true); - } - - public void - media_thumbnail(API.Callback? cb, - string server_name, - string media_id, - uint width, - uint height, - ResizeMethod method) - throws Matrix.Error - { - string path = "download/" - + Soup.URI.encode(server_name, null) - + "/" - + Soup.URI.encode(media_id, null); - HashTable parms; - - parms = _create_query_params(); - - if (width > 0) { - parms.replace("width", "%u".printf(width)); - } - - if (height > 0) { - parms.replace("height", "%u".printf(height)); - } - - if (method != ResizeMethod.DEFAULT) { - switch (method) { - case ResizeMethod.CROP: - parms.replace("method", "crop"); - - break; - - case ResizeMethod.SCALE: - parms.replace("method", "scale"); - - break; - - // This is here to prevent compiler warnings - case ResizeMethod.DEFAULT: break; - } - } - - _send(cb, - CallType.MEDIA, "GET", path, - parms, null, null, null, true); - } - - public void - media_upload(API.Callback? cb, - string? content_type, - owned ByteArray content) - throws Matrix.Error - { - _send(cb, - CallType.MEDIA, "POST", "upload", - null, content_type, null, content, false); - } - - /* Presence */ - - public void - get_presence_list(API.Callback? cb, string user_id) - throws Matrix.Error - { - string path = "presence/list/" + Soup.URI.encode(user_id, null); - - _send(cb, - CallType.API, "GET", path, - null, null, null, null, false); - } - - public void - update_presence_list(API.Callback? cb, - string user_id, - string[] drop_ids, - string[] invite_ids) - throws Matrix.Error - { - Json.Builder builder; - string path = "presence/" - + Soup.URI.encode(user_id, null) - + "/status"; - - builder = new Json.Builder(); - builder.begin_object(); - - if (drop_ids.length > 0) { - builder.set_member_name("drop"); - builder.begin_array(); - - foreach (var entry in drop_ids) { - builder.add_string_value(entry); - } - - builder.end_array(); - } - - if (invite_ids.length > 0) { - builder.set_member_name("invite"); - builder.begin_array(); - - foreach (var entry in invite_ids) { - builder.add_string_value(entry); - } - - builder.end_array(); - } - - builder.end_object(); - - _send(cb, - CallType.API, "POST", path, - null, null, builder.get_root(), null, false); - } - - public void - get_presence(API.Callback? cb, - string user_id) - throws Matrix.Error - { - string path = "presence/" - + Soup.URI.encode(user_id, null) - + "/status"; - - _send(cb, - CallType.API, "GET", path, - null, null, null, null, false); - } - - public void - set_presence(API.Callback? cb, - string user_id, - Presence presence, - string? status_message) - throws Matrix.Error - { - Json.Builder builder; - string path = "presence/" - + Soup.URI.encode(user_id, null) - + "/status"; - - builder = new Json.Builder(); - builder.begin_object(); - - builder.set_member_name("presence"); - builder.add_string_value( - _g_enum_value_to_nick(typeof(Matrix.Presence), - presence)); - - if (status_message != null) { - builder.set_member_name("status_msg"); - builder.add_string_value(status_message); - } - - _send(cb, - CallType.API, "POST", path, - null, null, builder.get_root(), null, false); - } - - /* Push notifications */ - - public void - update_pusher(API.Callback? cb, - Matrix.Pusher pusher) - throws Matrix.Error - { - Json.Node? pusher_node; - - if ((pusher_node = pusher.get_json_node()) != null) { - return; - } - - _send(cb, - CallType.API, "POST", "pushers/set", - null, null, pusher_node, null, false); - } - - public void - get_pushers(API.Callback? cb) - throws Matrix.Error - { - _send(cb, - CallType.API, "GET", "pushrules", - null, null, null, null, false); - } - - private static string - _pusher_url(string scope, - PusherKind kind, - string rule_id) - { - return "pushrules/" - + Soup.URI.encode(scope, null) - + "/" - + Soup.URI.encode(_g_enum_value_to_nick( - typeof(Matrix.PusherKind), - kind), - null) - + "/" - + Soup.URI.encode(rule_id, null); - } - - private void - _pusher_modif(API.Callback? cb, - string method, - string scope, - PusherKind kind, - string rule_id) - throws Matrix.Error - { - _send(cb, - CallType.API, method, - _pusher_url(scope, kind, rule_id), - null, null, null, null, false); - } - - public void - delete_pushrule(API.Callback? cb, - string scope, - PusherKind kind, - string rule_id) - throws Matrix.Error - { - _pusher_modif(cb, "DELETE", scope, kind, rule_id); - } - - public void - get_pushrule(API.Callback? cb, - string scope, - PusherKind kind, - string rule_id) - throws Matrix.Error - { - _pusher_modif(cb, "GET", scope, kind, rule_id); - } - - public void - add_pushrule(API.Callback? cb, - string scope, - PusherKind kind, - string rule_id, - string? before, - string? after, - string[] actions, - PusherConditionKind[] conditions) - throws Matrix.Error - { - Json.Builder builder; - HashTable parms = _create_query_params(); - - if (before != null) { - parms.replace("before", before); - } - - if (after != null) { - parms.replace("after", after); - } - - builder = new Json.Builder(); - builder.begin_object(); - - builder.set_member_name("actions"); - builder.begin_array(); - foreach (var entry in actions) { - builder.add_string_value(entry); - } - builder.end_array(); - - if (conditions.length > 0) { - builder.set_member_name("conditions"); - builder.begin_array(); - - foreach (var entry in conditions) { - string? kind_string = _g_enum_value_to_nick( - typeof(Matrix.PusherConditionKind), - entry); - - if (kind_string == null) { - warning("Invalid condition kind"); - - return; - } - - builder.begin_object(); - builder.set_member_name("kind"); - builder.add_string_value(kind_string); - builder.end_object(); - } - - builder.end_array(); - } - - builder.end_object(); - - _send(cb, - CallType.API, "GET", - _pusher_url(scope, kind, rule_id), - parms, null, builder.get_root(), null, false); - } - - public void - toggle_pushrule(API.Callback? cb, - string scope, - PusherKind kind, - string rule_id, - bool enabled) - throws Matrix.Error - { - Json.Builder builder; - - builder = new Json.Builder(); - builder.begin_object(); - - builder.set_member_name("enabled"); - builder.add_boolean_value(enabled); - - builder.end_object(); - - _send(cb, - CallType.API, "GET", - _pusher_url(scope, kind, rule_id), - null, null, builder.get_root(), null, false); - } - - public void - get_pushrules(API.Callback? cb) - throws Matrix.Error - { - _send(cb, - CallType.API, "GET", "pushrules", - null, null, null, null, false); - } - - /* Room creation */ - - public void - create_room(API.Callback? cb, - RoomPreset preset, - string? room_name, - string? room_alias, - string? topic, - RoomVisibility visibility, - Json.Node? creation_content, - Matrix.Event.State[] initial_state, - string[] invitees, - 3PidCredential[] invite_3pids) - throws Matrix.Error - { - Json.Builder builder = new Json.Builder(); - - builder.begin_object(); - - if (creation_content != null) { - builder.set_member_name("creation_content"); - builder.add_value(creation_content); - } - - if (initial_state.length > 0) { - builder.set_member_name("initial_state"); - builder.begin_array(); - - foreach (var entry in initial_state) { - builder.add_value(entry.json); - } - - builder.end_array(); - } - - if (invitees.length > 0) { - builder.set_member_name("invite"); - builder.begin_array(); - - foreach (var entry in invitees) { - builder.add_string_value(entry); - } - - builder.end_array(); - } - - if (invite_3pids.length > 0) { - builder.set_member_name("invite_3pid"); - builder.begin_array(); - - foreach (var entry in invite_3pids) { - try { - builder.add_value(entry.get_json_node()); - // TODO exceptions should be handled here somehow - } catch (Matrix.Error e) {} - } - - builder.end_array(); - } - - if (room_name != null) { - builder.set_member_name("name"); - builder.add_string_value(room_name); - } - - if (preset != RoomPreset.NONE) { - string? preset_string = _g_enum_value_to_nick( - typeof(RoomPreset), - preset); - - if (preset_string != null) { - builder.set_member_name("preset"); - builder.add_string_value(preset_string); - } else { - warning("Invalid room preset type"); - } - } - - if (room_alias != null) { - builder.set_member_name("room_alias_name"); - builder.add_string_value(room_alias); - } - - if (topic != null) { - builder.set_member_name("topic"); - builder.add_string_value(topic); - } - - if (visibility != RoomVisibility.DEFAULT) { - string? visibility_string = _g_enum_value_to_nick( - typeof(RoomVisibility), visibility); - - if (visibility_string != null) { - builder.set_member_name("visibility"); - builder.add_string_value(visibility_string); - } else { - warning("Invalid room visibility type"); - } - } - - _send(cb, - CallType.API, "POST", "createRoom", - null, null, builder.get_root(), null, false); - } - - /* Room directory */ - - public void - delete_room_alias(API.Callback? cb, - string room_alias) - throws Matrix.Error - { - string path = "room/" + Soup.URI.encode(room_alias, null); - - _send(cb, - CallType.API, "DELETE", path, - null, null, null, null, false); - } - - public void - get_room_id(API.Callback? cb, - string room_alias) - throws Matrix.Error - { - string path = "room/" + Soup.URI.encode(room_alias, null); - - _send(cb, - CallType.API, "GET", path, - null, null, null, null, false); - } - - public void - create_room_alias(API.Callback? cb, - string room_id, - string room_alias) - throws Matrix.Error - { - Json.Builder builder; - string path = "room/" + Soup.URI.encode(room_alias, null); - - builder = new Json.Builder(); - builder.begin_object(); - - builder.set_member_name("room_id"); - builder.add_string_value(room_id); - - builder.end_object(); - - _send(cb, - CallType.API, "PUT", path, - null, null, builder.get_root(), null, false); - } - - /* Room discovery */ - - public void - list_public_rooms(API.Callback? cb) - throws Matrix.Error - { - _send(cb, - CallType.API, "GET", "publicRooms", - null, null, null, null, false); - } - - /* Room membership */ - - public void - ban_user(API.Callback? cb, - string room_id, - string user_id, - string? reason) - throws Matrix.Error - { - string path = "rooms/" - + Soup.URI.encode(room_id, null) - + "/ban"; - var builder = new Json.Builder(); - - builder.begin_object(); - - builder.set_member_name("user_id"); - builder.add_string_value(user_id); - - if (reason != null) { - builder.set_member_name("reason"); - builder.add_string_value(reason); - } - - builder.end_object(); - - _send(cb, - CallType.API, "POST", path, - null, null, builder.get_root(), null, false); - } - - public void - unban_user(API.Callback? cb, - string room_id, - string user_id) - throws Matrix.Error - { - string path = "rooms/" - + Soup.URI.encode(room_id, null) - + "/unban"; - var builder = new Json.Builder(); - - builder.begin_object(); - - builder.set_member_name("user_id"); - builder.add_string_value(user_id); - - builder.end_object(); - - _send(cb, CallType.API, "POST", path, null, null, builder.get_root(), null, false); - } - - public void - forget_room(API.Callback? cb, - string room_id) - throws Matrix.Error - { - string path = "rooms/" - + Soup.URI.encode(room_id, null) - + "/forget"; - - _send(cb, - CallType.API, "POST", path, - null, null, null, null, false); - } - - public void - invite_user_3rdparty(API.Callback? cb, - string room_id, - Matrix.3PidCredential credential) - throws Matrix.Error - { - string path = "rooms/" - + Soup.URI.encode(room_id, null) - + "/invite"; - Json.Node? body; - - if ((body = credential.get_json_node()) == null) { - return; - } - - _send(cb, - CallType.API, "POST", path, - null, null, body, null, false); - } - - public void - invite_user(API.Callback? cb, - string room_id, - string user_id) - throws Matrix.Error - { - var builder = new Json.Builder(); - string path = "rooms/" - + Soup.URI.encode(room_id, null) - + "/invite"; - - builder.begin_object(); - - builder.set_member_name("user_id"); - builder.add_string_value(user_id); - - builder.end_object(); - - _send(cb, - CallType.API, "POST", path, - null, null, builder.get_root(), null, false); - } - - public void - join_room(API.Callback? cb, - string room_id) - throws Matrix.Error - { - string path = "rooms/" - + Soup.URI.encode(room_id, null) - + "/join"; - - _send(cb, - CallType.API, "POST", path, - null, null, null, null, false); - } - - public void - leave_room(API.Callback? cb, - string room_id) - throws Matrix.Error - { - string path = "rooms/" - + Soup.URI.encode(room_id, null) - + "/leave"; - - _send(cb, - CallType.API, "POST", path, - null, null, null, null, false); - } - - public void - join_room_id_or_alias(API.Callback? cb, string room_id_or_alias) - throws Matrix.Error - { - var path = "join/" + Soup.URI.encode(room_id_or_alias, null); - - _send(cb, CallType.API, "POST", path, - null, null, null, null, false); - } - - public void - kick_user(API.Callback? cb, - string room_id, string user_id, string? reason) - throws Matrix.Error - { - var path = "rooms/" + Soup.URI.encode(room_id, null) + "/kick"; - var builder = new Json.Builder(); - - builder.begin_object(); - - builder.set_member_name("user_id"); - builder.add_string_value(user_id); - - if (reason != null) { - builder.set_member_name("reason"); - builder.add_string_value(reason); - } - - builder.end_object(); - - _send(cb, CallType.API, "POST", path, - null, null, builder.get_root(), null, false); - } - - /* Room participation */ - - public void - event_stream(API.Callback? cb, - string? from_token, - ulong timeout) - throws Matrix.Error - { - HashTable parms = _create_query_params(); - - if (from_token != null) { - parms.replace("from", from_token); - } - - if (timeout > 0) { - parms.replace("timeout", "%lu".printf(timeout)); - } - - _send(cb, - CallType.API, "GET", "events", - parms, null, null, null, false); - } - - public void - get_event(API.Callback? cb, - string event_id) - throws Matrix.Error - { - string path = "events/" + Soup.URI.encode(event_id, null); - - _send(cb, - CallType.API, "GET", path, - null, null, null, null, false); - } - - public void - initial_sync(API.Callback? cb, - uint limit, - bool archived) - throws Matrix.Error - { - HashTable parms = _create_query_params(); - - if (limit != 0) { - parms.replace("limit", "%u".printf(limit)); - } - - if (archived) { - parms.replace("archived", "true"); - } - - _send(cb, - CallType.API, "GET", "initialSync", - parms, null, null, null, false); - } - - public void - get_event_context(API.Callback? cb, - string room_id, - string event_id, - uint limit) - throws Matrix.Error - { - string path = "rooms/" - + Soup.URI.encode(room_id, null) - + "/" - + Soup.URI.encode(event_id, null); - HashTable? parms = null; - - if (limit > 0) { - parms = _create_query_params(); - - parms.replace("limit", "%u".printf(limit)); - } - - _send(cb, - CallType.API, "GET", path, - parms, null, null, null, false); - } - - public void - initial_sync_room(API.Callback? cb, - string room_id) - throws Matrix.Error - { - string path = "rooms/" - + Soup.URI.encode(room_id, null) - + "/initialSync"; - - _send(cb, - CallType.API, "GET", path, - null, null, null, null, false); - } - - public void - list_room_members(API.Callback? cb, - string room_id) - throws Matrix.Error - { - string path = "rooms/" - + Soup.URI.encode(room_id, null) - + "/members"; - - _send(cb, - CallType.API, "GET", path, - null, null, null, null, false); - } - - public void - list_room_messages(API.Callback? cb, - string room_id, - string from_token, - EventDirection direction, - uint limit) - throws Matrix.Error - { - string path = "rooms/" - + Soup.URI.encode(room_id, null) - + "/messages"; - HashTable parms = _create_query_params(); - - parms.replace("from", from_token); - - switch (direction) { - case EventDirection.BACKWARD: - parms.replace("dir", "b"); - - break; - - case EventDirection.FORWARD: - parms.replace("dir", "f"); - - break; - } - - if (limit > 0) { - parms.replace("limit", "%u".printf(limit)); - } - - _send(cb, - CallType.API, "GET", path, - parms, null, null, null, false); - } - - public void - send_event_receipt(API.Callback? cb, - string room_id, - ReceiptType receipt_type, - string event_id, - Json.Node receipt) - throws Matrix.Error - { - string path = "rooms/" - + Soup.URI.encode(room_id, null) - + "/receipt/" - + Soup.URI.encode(_g_enum_value_to_nick( - typeof(ReceiptType), - receipt_type), - null) - + "/" - + Soup.URI.encode(event_id, null); - - _send(cb, - CallType.API, "POST", path, - null, null, receipt, null, false); - } - - public void - redact_event(API.Callback? cb, - string room_id, - string event_id, - string txn_id, - string? reason) - throws Matrix.Error - { - Json.Node? body = null; - string path = "rooms/%s/redact/%s/%s".printf( - Soup.URI.encode(room_id, null), - Soup.URI.encode(event_id, null), - Soup.URI.encode(txn_id, null)); - - if (reason != null) { - var builder = new Json.Builder(); - builder.begin_object(); - - builder.set_member_name("reason"); - builder.add_string_value(reason); - - builder.end_object(); - body = builder.get_root(); - } - - _send(cb, - CallType.API, "PUT", path, - null, null, body, null, false); - } - - public void - send_event(API.Callback? cb, - string room_id, - string event_type, - string txn_id, - owned Json.Node content) - throws Matrix.Error - { - string path = "rooms/%s/send/%s/%s".printf( - Soup.URI.encode(room_id, null), - Soup.URI.encode(event_type, null), - Soup.URI.encode(txn_id, null)); - - _send(cb, - CallType.API, "PUT", path, - null, null, content, null, false); - } - - public void - get_room_state(API.Callback? cb, - string room_id, - string? event_type, - string? state_key) - throws Matrix.Error - { - string path = "rooms/%s/state".printf( - Soup.URI.encode(room_id, null)); - - if (event_type != null) { - path += "/%s".printf(Soup.URI.encode(event_type, null)); - } - - if (state_key != null) { - path += "/%s".printf(Soup.URI.encode(state_key, null)); - } - - _send(cb, - CallType.API, "GET", path, - null, null, null, null, false); - } - - public void - send_state_event(API.Callback? cb, - string room_id, - string event_type, - string? state_key, - owned Json.Node content) - throws Matrix.Error - { - string path = "rooms/%s/state/%s".printf( - Soup.URI.encode(room_id, null), - Soup.URI.encode(event_type, null)); - - if (state_key != null) { - path += "/%s".printf(Soup.URI.encode(state_key, null)); - } - - _send(cb, - CallType.API, "PUT", path, - null, null, content, null, false); - } - - public void - notify_room_typing(API.Callback? cb, - string user_id, - string room_id, - uint timeout, - bool typing) - throws Matrix.Error - { - string path = "rooms/%s/typing/%s".printf( - Soup.URI.encode(room_id, null), - Soup.URI.encode(user_id, null)); - var builder = new Json.Builder(); - - builder.begin_object(); - - if (timeout > 0) { - builder.set_member_name("timeout"); - builder.add_int_value(timeout); - } - - builder.set_member_name("typing"); - builder.add_boolean_value(typing); - - _send(cb, - CallType.API, "PUT", path, - null, null, builder.get_root(), null, false); - } - - public void - sync(API.Callback? cb, - string? filter_id, - Filter? filter, - string? since, - bool full_state, - bool set_presence, - ulong timeout) - throws Matrix.Error - { - HashTable parms = _create_query_params(); - - if ((filter_id != null) && (filter != null)) { - throw new Matrix.Error.BAD_REQUEST( - "Cannot set both filter_id and filter"); - } - - if (filter_id != null) { - parms.replace("filter", filter_id); - } - - if (filter != null) { - parms.replace("filter", filter.get_json_data(null)); - } - - if (since != null) { - parms.replace("since", since); - } - - parms.replace("full_state", (full_state) ? "true" : "false"); - - if (!set_presence) { - parms.replace("set_presence", "offline"); - } - - if (timeout != 0) { - parms.replace("timeout", "%lu".printf(timeout)); - } - - _send(cb, - CallType.API, "GET", "sync", - parms, null, null, null, false); - } - - public void - create_filter(API.Callback? cb, - string user_id, - Filter filter) - throws Matrix.Error - { - string path = "user/%s/filter".printf( - Soup.URI.encode(user_id, null)); - - _send(cb, - CallType.API, "POST", path, - null, null, filter.get_json_node(), null, false); - } - - public void - download_filter(API.Callback? cb, - string user_id, - string filter_id) - throws Matrix.Error - { - string path = "user/%s/filter/%s".printf( - Soup.URI.encode(user_id, null), - Soup.URI.encode(filter_id, null)); - - _send(cb, - CallType.API, "GET", path, - null, null, null, null, false); - } - - /* Search */ - - public void - search(Matrix.API.Callback? cb, - string? next_batch, - SearchCategories search_categories) - throws Matrix.Error - { - HashTable? parms = null; - - if (next_batch == null) { - parms = _create_query_params(); - - parms.replace("next_batch", next_batch); - } - - _send(cb, - CallType.API, "POST", "search", - parms, null, search_categories.get_json_node(), null, false); - } - - /* Server administration */ - - public void - whois(API.Callback? cb, string user_id) - throws Matrix.Error - { - string path = "admin/whois/" + Soup.URI.encode(user_id, null); - - _send(cb, - CallType.API, "GET", path, - null, null, null, null, false); - } - - public void - versions(API.Callback? cb) - throws Matrix.Error - { - _send(cb, - CallType.API, "GET", "versions", - null, null, null, null, false); - } - - /* Session management */ - - public void - login(API.Callback? cb, - string login_type, - Json.Node? content) - throws Matrix.Error - { - Json.Node body = _json_node_deep_copy(content); - - body.get_object().set_string_member("type", login_type); - - _send(cb, - CallType.API, "POST", "login", - null, null, body, null, false); - } - - public void - token_refresh(API.Callback? cb, - string? refresh_token) - throws Matrix.Error - { - if ((refresh_token == null) && (this.refresh_token == null)) { - throw new Matrix.Error.M_MISSING_TOKEN("No token available"); - } - - var builder = new Json.Builder(); - - builder.begin_object(); - - builder.set_member_name("refresh_token"); - builder.add_string_value((refresh_token != null) - ? refresh_token - : this.refresh_token); - - builder.end_object(); - - _send(cb, - CallType.API, "POST", "tokenrefresh", - null, null, builder.get_root(), null, false); - } - - public void - logout(API.Callback? cb) - throws Matrix.Error - { - _send(cb, - CallType.API, "POST", "logout", - null, null, null, null, false); - } - - /* User data */ - - public void - get_3pids(API.Callback? cb) - throws Matrix.Error - { - _send(cb, - CallType.API, "GET", "account/3pid", - null, null, null, null, false); - } - - public void - add_3pid(API.Callback? cb, - bool bind_creds, - Matrix.3PidCredential creds) - throws Matrix.Error - { - var id_node = creds.get_json_node(); - var builder = new Json.Builder(); - - builder.begin_object(); - - builder.set_member_name("bind"); - builder.add_boolean_value(bind_creds); - - builder.set_member_name("threePidCreds"); - builder.add_value(id_node); - - _send(cb, - CallType.API, "POST", "account/3pid", - null, null, builder.get_root(), null, false); - } - - public void - change_password(API.Callback? cb, - string new_password) - throws Matrix.Error - { - var builder = new Json.Builder(); - - builder.begin_object(); - - builder.set_member_name("new_password"); - builder.add_string_value(new_password); - - builder.end_object(); - - _send(cb, - CallType.API, "POST", "account/password", - null, null, builder.get_root(), null, false); - } - - public void - get_profile(API.Callback? cb, - string user_id) - throws Matrix.Error - { - string path = "profile/%s".printf(Soup.URI.encode(user_id, null)); - - _send(cb, - CallType.API, "GET", path, - null, null, null, null, false); - } - - public void - get_avatar_url(API.Callback? cb, - string user_id) - throws Matrix.Error - { - string path = "profile/%s/avatar_url".printf( - Soup.URI.encode(user_id, null)); - - _send(cb, - CallType.API, "GET", path, - null, null, null, null, false); - } - - public void - set_avatar_url(API.Callback? cb, - string user_id, - string avatar_url) - throws Matrix.Error - { - string path = "profile/%s/avatar_url".printf( - Soup.URI.encode(user_id, null)); - var builder = new Json.Builder(); - - builder.begin_object(); - - builder.set_member_name("avatar_url"); - builder.add_string_value(avatar_url); - - builder.end_object(); - - _send(cb, - CallType.API, "PUT", path, - null, null, builder.get_root(), null, false); - } - - public void - get_display_name(API.Callback? cb, - string user_id) - throws Matrix.Error - { - string path = "profile/%s/displayname".printf( - Soup.URI.encode(user_id, null)); - - _send(cb, - CallType.API, "GET", path, - null, null, null, null, false); - } - - public void - set_display_name(API.Callback? cb, - string user_name, - string display_name) - throws Matrix.Error - { - string path = "profile/%s/displayname".printf( - Soup.URI.encode(user_id, null)); - var builder = new Json.Builder(); - - builder.begin_object(); - - builder.set_member_name("displayname"); - builder.add_string_value(display_name); - - builder.end_object(); - - _send(cb, - CallType.API, "PUT", path, - null, null, builder.get_root(), null, false); - } - - public void - register_account(API.Callback? cb, - AccountKind account_kind, - bool bind_email, - string? username, - string password) - throws Matrix.Error - { - HashTable? parms = null; - var builder = new Json.Builder(); - - builder.begin_object(); - - builder.set_member_name("bind_email"); - builder.add_boolean_value(bind_email); - - if (username != null) { - builder.set_member_name("username"); - builder.add_string_value(username); - } - - builder.set_member_name("password"); - builder.add_string_value(password); - - builder.end_object(); - - if (account_kind != AccountKind.DEFAULT) { - string? kind_string = _g_enum_value_to_nick( - typeof(Matrix.AccountKind), account_kind); - - if (kind_string != null) { - parms = _create_query_params(); - - parms.replace("kind", kind_string); - } - } - - _send(cb, - CallType.API, "POST", "register", - parms, null, builder.get_root(), null, false); - } - - public void - set_account_data(API.Callback? cb, - string user_id, - string? room_id, - string event_type, - owned Json.Node content) - throws Matrix.Error - { - string path; - - if (room_id != null) { - path = "user/%s/rooms/%s/account_data/%s".printf( - Soup.URI.encode(user_id, null), - Soup.URI.encode(room_id, null), - Soup.URI.encode(event_type, null)); - } else { - path = "user/%s/account_data/%s".printf( - Soup.URI.encode(user_id, null), - Soup.URI.encode(event_type, null)); - } - - _send(cb, - CallType.API, "PUT", path, - null, null, content, null, false); - } - - public void - get_room_tags(API.Callback? cb, - string user_id, - string room_id) - throws Matrix.Error - { - string path = "user/%s/rooms/%s/tags".printf( - Soup.URI.encode(user_id, null), - Soup.URI.encode(room_id, null)); - - _send(cb, - CallType.API, "GET", path, - null, null, null, null, false); - } - - public void - delete_room_tag(API.Callback? cb, - string user_id, - string room_id, - string tag) - throws Matrix.Error - { - string path = "user/%s/rooms/%s/tags/%s".printf( - Soup.URI.encode(user_id, null), - Soup.URI.encode(room_id, null), - Soup.URI.encode(tag, null)); - - _send(cb, - CallType.API, "DELETE", path, - null, null, null, null, false); - } - - public void - add_room_tag(API.Callback? cb, - string user_id, - string room_id, - string tag, - owned Json.Node? content) - throws Matrix.Error - { - string path = "user/%s/rooms/%s/tags/%s".printf( - Soup.URI.encode(user_id, null), - Soup.URI.encode(room_id, null), - Soup.URI.encode(tag, null)); - - _send(cb, - CallType.API, "PUT", path, - null, null, content, null, false); - } - - public void - deactivate_account(API.Callback? cb, string? session, string? login_type) - throws Matrix.Error - { - Json.Builder? builder = null; - - if (login_type != null) { - builder = new Json.Builder(); - - builder.begin_object(); - - builder.set_member_name("auth"); - builder.begin_object(); - - if (session != null) { - builder.set_member_name("session"); - builder.add_string_value(session); - } - - builder.set_member_name("type"); - builder.add_string_value(login_type); - - builder.end_object(); - builder.end_object(); - } - - _send(cb, CallType.API, "POST", "account/deactivate", - null, null, (builder != null) ? builder.get_root() : null, null, false); - } - - /* VoIP */ - - public void - get_turn_server(API.Callback? cb) - throws Matrix.Error - { - _send(cb, - CallType.API, "GET", "voip/turnServer", - null, null, null, null, false); - } - - /* Non-spec methods */ - - public void - abort_pending() - { - _soup_session.abort(); - } -} diff --git a/src/test-api-client.c b/src/test-api-client.c index fb108f1..001547a 100644 --- a/src/test-api-client.c +++ b/src/test-api-client.c @@ -20,7 +20,7 @@ #include #include -#include "matrix-glib.h" +#include "matrix-http-api.h" #define LOG_DOMAIN "Matrix-Test-Client" diff --git a/vapi/c-api.vapi b/vapi/c-api.vapi index 5d83efa..7fc386e 100644 --- a/vapi/c-api.vapi +++ b/vapi/c-api.vapi @@ -704,6 +704,250 @@ namespace Matrix { throws Matrix.Error; } + [CCode (lower_case_csuffix = "http_api", cheader_filename = "matrix-http-api.h")] + public class HTTPAPI : GLib.Object, API { + public string base_url { get; set; } + public bool validate_certificate { get; set; } + protected string? _user_id; + public string? user_id { get; default = null; } + public string? token { get; set; default = null; } + public string? refresh_token { get; set; default = null; } + protected string? _homeserver; + public string? homeserver { get; default = null; } + + protected HTTPAPI(string base_url, string? token = null, string? refresh_token = null); + + /* Media */ + + public void media_download(API.Callback? cb, string server_name, string media_id) + throws Matrix.Error; + + public void media_thumbnail(API.Callback? cb, string server_name, string media_id, uint width, uint height, ResizeMethod method) + throws Matrix.Error; + + public void media_upload(API.Callback? cb, string? content_type, owned GLib.ByteArray content) + throws Matrix.Error; + + /* Presence */ + + public void get_presence_list(API.Callback? cb, string user_id) + throws Matrix.Error; + + public void + update_presence_list(API.Callback? cb, string user_id, string[] drop_ids, string[] invite_ids) + throws Matrix.Error; + + public void get_presence(API.Callback? cb, string user_id) + throws Matrix.Error; + + public void set_presence(API.Callback? cb, string user_id, Presence presence, string? status_message) + throws Matrix.Error; + + /* Push notifications */ + + public void update_pusher(API.Callback? cb, Matrix.Pusher pusher) + throws Matrix.Error; + + public void get_pushers(API.Callback? cb) + throws Matrix.Error; + + + private void _pusher_modif(API.Callback? cb, string method, string scope, PusherKind kind, string rule_id) + throws Matrix.Error; + + public void delete_pushrule(API.Callback? cb, string scope, PusherKind kind, string rule_id) + throws Matrix.Error; + + public void get_pushrule(API.Callback? cb, string scope, PusherKind kind, string rule_id) + throws Matrix.Error; + + public void add_pushrule(API.Callback? cb, string scope, PusherKind kind, string rule_id, string? before, string? after, string[] actions, PusherConditionKind[] conditions) + throws Matrix.Error; + + public void toggle_pushrule(API.Callback? cb, string scope, PusherKind kind, string rule_id, bool enabled) + throws Matrix.Error; + + public void get_pushrules(API.Callback? cb) + throws Matrix.Error; + + /* Room creation */ + + public void create_room(API.Callback? cb, RoomPreset preset, string? room_name, string? room_alias, string? topic, RoomVisibility visibility, Json.Node? creation_content, Matrix.Event.State[] initial_state, string[] invitees, 3PidCredential[] invite_3pids) + throws Matrix.Error; + + /* Room directory */ + + public void delete_room_alias(API.Callback? cb, string room_alias) + throws Matrix.Error; + + public void get_room_id(API.Callback? cb, string room_alias) + throws Matrix.Error; + + public void create_room_alias(API.Callback? cb, string room_id, string room_alias) + throws Matrix.Error; + + /* Room discovery */ + + public void list_public_rooms(API.Callback? cb) + throws Matrix.Error; + + /* Room membership */ + + public void ban_user(API.Callback? cb, string room_id, string user_id, string? reason) + throws Matrix.Error; + + public void unban_user(API.Callback? cb, string room_id, string user_id) + throws Matrix.Error; + + public void forget_room(API.Callback? cb, string room_id) + throws Matrix.Error; + + public void invite_user_3rdparty(API.Callback? cb, string room_id, Matrix.3PidCredential credential) + throws Matrix.Error; + + public void invite_user(API.Callback? cb, string room_id, string user_id) + throws Matrix.Error; + + public void join_room(API.Callback? cb, string room_id) + throws Matrix.Error; + + public void leave_room(API.Callback? cb, string room_id) + throws Matrix.Error; + + public void join_room_id_or_alias(API.Callback? cb, string room_id_or_alias) + throws Matrix.Error; + + public void kick_user(API.Callback? cb, string room_id, string user_id, string? reason) + throws Matrix.Error; + + /* Room participation */ + + public void event_stream(API.Callback? cb, string? from_token, ulong timeout) + throws Matrix.Error; + + public void get_event(API.Callback? cb, string event_id) + throws Matrix.Error; + + public void initial_sync(API.Callback? cb, uint limit, bool archived) + throws Matrix.Error; + + public void get_event_context(API.Callback? cb, string room_id, string event_id, uint limit) + throws Matrix.Error; + + public void initial_sync_room(API.Callback? cb, string room_id) + throws Matrix.Error; + + public void list_room_members(API.Callback? cb, string room_id) + throws Matrix.Error; + + public void list_room_messages(API.Callback? cb, string room_id, string from_token, EventDirection direction, uint limit) + throws Matrix.Error; + + public void send_event_receipt(API.Callback? cb, string room_id, ReceiptType receipt_type, string event_id, Json.Node receipt) + throws Matrix.Error; + + public void redact_event(API.Callback? cb, string room_id, string event_id, string txn_id, string? reason) + throws Matrix.Error; + + public void send_event(API.Callback? cb, string room_id, string event_type, string txn_id, owned Json.Node content) + throws Matrix.Error; + + public void get_room_state(API.Callback? cb, string room_id, string? event_type, string? state_key) + throws Matrix.Error; + + public void send_state_event(API.Callback? cb, string room_id, string event_type, string? state_key, owned Json.Node content) + throws Matrix.Error; + + public void notify_room_typing(API.Callback? cb, string user_id, string room_id, uint timeout, bool typing) + throws Matrix.Error; + + public void sync(API.Callback? cb, string? filter_id, Filter? filter, string? since, bool full_state, bool set_presence, ulong timeout) + throws Matrix.Error; + + public void create_filter(API.Callback? cb, string user_id, Filter filter) + throws Matrix.Error; + + public void download_filter(API.Callback? cb, string user_id, string filter_id) + throws Matrix.Error; + + /* Search */ + + public void search(Matrix.API.Callback? cb, string? next_batch, SearchCategories search_categories) + throws Matrix.Error; + + /* Server administration */ + + public void whois(API.Callback? cb, string user_id) + throws Matrix.Error; + + public void versions(API.Callback? cb) + throws Matrix.Error; + + /* Session management */ + + public void login(API.Callback? cb, string login_type, Json.Node? content) + throws Matrix.Error; + + public void token_refresh(API.Callback? cb, string? refresh_token) + throws Matrix.Error; + + public void logout(API.Callback? cb) + throws Matrix.Error; + + /* User data */ + + public void get_3pids(API.Callback? cb) + throws Matrix.Error; + + public void add_3pid(API.Callback? cb, bool bind_creds, Matrix.3PidCredential creds) + throws Matrix.Error; + + public void change_password(API.Callback? cb, string new_password) + throws Matrix.Error; + + public void get_profile(API.Callback? cb, string user_id) + throws Matrix.Error; + + public void get_avatar_url(API.Callback? cb, string user_id) + throws Matrix.Error; + + public void set_avatar_url(API.Callback? cb, string user_id, string avatar_url) + throws Matrix.Error; + + public void get_display_name(API.Callback? cb, string user_id) + throws Matrix.Error; + + public void set_display_name(API.Callback? cb, string user_name, string display_name) + throws Matrix.Error; + + public void register_account(API.Callback? cb, AccountKind account_kind, bool bind_email, string? username, string password) + throws Matrix.Error; + + public void set_account_data(API.Callback? cb, string user_id, string? room_id, string event_type, owned Json.Node content) + throws Matrix.Error; + + public void get_room_tags(API.Callback? cb, string user_id, string room_id) + throws Matrix.Error; + + public void delete_room_tag(API.Callback? cb, string user_id, string room_id, string tag) + throws Matrix.Error; + + public void add_room_tag(API.Callback? cb, string user_id, string room_id, string tag, owned Json.Node? content) + throws Matrix.Error; + + public void deactivate_account(API.Callback? cb, string? session, string? login_type) + throws Matrix.Error; + + /* VoIP */ + + public void get_turn_server(API.Callback? cb) + throws Matrix.Error; + + /* Non-spec methods */ + + public void abort_pending(); + } + /** * The major version number of the Matrix.org GLib SDK. */