diff options
author | Richard van der Hoff <richard@matrix.org> | 2015-10-29 12:29:07 +0000 |
---|---|---|
committer | Richard van der Hoff <richard@matrix.org> | 2015-10-29 12:29:07 +0000 |
commit | 32e9c376d3a25e527327974dd97e2313d430a5fd (patch) | |
tree | 4876a9d24c819bb83fca2b21761db3806b066d37 /matrix-api.c | |
parent | 2498e41374edc940d6de474c11e0530fcc83752f (diff) | |
download | purple-matrix-32e9c376d3a25e527327974dd97e2313d430a5fd.tar.gz |
Improve error-handling in API requests
Try to deal better with errors which occur during API requests, in terms of
reporting them to the user/logs, and in terms of not writing to freed
structures.
Also do some sanity-checking of the supplied homeserver URL: make sure it is
https:// or http://, and make sure we get the right number of '/'s.
Diffstat (limited to 'matrix-api.c')
-rw-r--r-- | matrix-api.c | 203 |
1 files changed, 121 insertions, 82 deletions
diff --git a/matrix-api.c b/matrix-api.c index c744a54..7821bf9 100644 --- a/matrix-api.c +++ b/matrix-api.c @@ -50,8 +50,6 @@ struct _MatrixApiRequestData { void matrix_api_error(MatrixConnectionData *conn, gpointer user_data, const gchar *error_message) { - purple_debug_info("matrixprpl", "Error calling API: %s\n", - error_message); if(strcmp(error_message, "cancelled") != 0) purple_connection_error_reason(conn->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_message); @@ -68,9 +66,6 @@ void matrix_api_bad_response(MatrixConnectionData *ma, gpointer user_data, const gchar *errcode = NULL, *error = NULL; gchar *error_message; - purple_debug_info("matrixprpl", "API gave response %i\n", - http_response_code); - if(json_root != NULL) { json_obj = matrix_json_node_get_object(json_root); errcode = matrix_json_object_get_string_member(json_obj, "errcode"); @@ -107,6 +102,7 @@ typedef struct { GString *current_header_name; GString *current_header_value; gchar *content_type; + gboolean got_headers; JsonParser *json_parser; } MatrixApiResponseParserData; @@ -198,6 +194,7 @@ static int _handle_headers_complete(http_parser *http_parser) { MatrixApiResponseParserData *response_data = http_parser->data; _handle_header_completed(response_data); + response_data->got_headers = TRUE; return 0; } @@ -244,6 +241,13 @@ static void matrix_api_complete(PurpleUtilFetchUrlData *url_data, int response_code = -1; JsonNode *root = NULL; + if(error_message) { + purple_debug_warning("matrixprpl", "Error from http request: %s\n", + error_message); + } else if (purple_debug_is_verbose()) { + purple_debug_info("matrixprpl", "Got response: %s\n", ret_data); + } + if(!error_message) { http_parser http_parser; http_parser_settings http_parser_settings; @@ -273,9 +277,16 @@ static void matrix_api_complete(PurpleUtilFetchUrlData *url_data, if(http_error != HPE_OK) { /* invalid response line */ purple_debug_info("matrixprpl", - "unable to parse HTTP response: %s\n", - http_errno_description(http_error)); - error_message = _("Error parsing response"); + "Error (%s) parsing HTTP response %s\n", + http_errno_description(http_error), ret_data); + error_message = _("Invalid response from homeserver"); + } else if (!response_data->got_headers) { + /* this will happen if we hit EOF before the end of the headers */ + purple_debug_info("matrixprpl", + "EOF before end of HTTP headers in response %s\n", + ret_data); + error_message = _("Invalid response from homeserver"); + } else { response_code = http_parser.status_code; } @@ -286,8 +297,11 @@ static void matrix_api_complete(PurpleUtilFetchUrlData *url_data, } if (error_message) { + purple_debug_info("matrixprpl", "Handling error: %s\n", error_message); (data->error_callback)(data->conn, data->user_data, error_message); } else if(response_code >= 300) { + purple_debug_info("matrixprpl", "API gave response %i\n", + response_code); (data->bad_response_callback)(data->conn, data->user_data, response_code, root); } else { @@ -303,46 +317,6 @@ static void matrix_api_complete(PurpleUtilFetchUrlData *url_data, * API entry points */ -/** - * Start an HTTP call to the API - * - * @param request request headers and body to send, or NULL for default GET - * @param max_len maximum number of bytes to return from the request. -1 for - * default (512K). - */ -static MatrixApiRequestData *matrix_api_start(const gchar *url, - const gchar *request, MatrixConnectionData *conn, - MatrixApiCallback callback, MatrixApiErrorCallback error_callback, - MatrixApiBadResponseCallback bad_response_callback, - gpointer user_data, gssize max_len) -{ - MatrixApiRequestData *data; - - data = g_new0(MatrixApiRequestData, 1); - data->conn = conn; - data->callback = callback; - data->error_callback = (error_callback == NULL ? - matrix_api_error : error_callback); - data->bad_response_callback = (bad_response_callback == NULL ? - matrix_api_bad_response : bad_response_callback); - data->user_data = user_data; - - data -> purple_data = purple_util_fetch_url_request_len_with_account( - conn -> pc -> account, - url, FALSE, NULL, TRUE, request, TRUE, max_len, matrix_api_complete, - data); - return data; -} - -void matrix_api_cancel(MatrixApiRequestData *data) -{ - purple_util_fetch_url_cancel(data -> purple_data); - data -> purple_data = NULL; - (data->error_callback)(data->conn, data->user_data, "cancelled"); - - g_free(data); -} - /* * Add proxy authentication headers to a request */ @@ -421,8 +395,10 @@ static void _parse_url(const gchar *url, const gchar **host, const gchar **path) * - libpurple only supports GET * - libpurple's purple_url_parse assumes that the path + querystring is * shorter than 256 bytes. + * + * @returns a gchar* which should be freed */ -static GString *_build_request(PurpleAccount *acct, const gchar *url, +static gchar *_build_request(PurpleAccount *acct, const gchar *url, const gchar *method, const gchar *body) { PurpleProxyInfo *gpi = purple_proxy_get_setup(acct); @@ -439,8 +415,7 @@ static GString *_build_request(PurpleAccount *acct, const gchar *url, _parse_url(url, &url_host, &url_path); /* we only support absolute URLs (with schemes) */ - g_assert(url_host != NULL); /* TODO: enforce this elsewhere */ - + g_assert(url_host != NULL); /* If we are connecting via a proxy, we should put the whole url * in the request line. (But synapse chokes if we do that on a direct @@ -462,9 +437,90 @@ static GString *_build_request(PurpleAccount *acct, const gchar *url, if(body != NULL) g_string_append(request_str, body); - return request_str; + return g_string_free(request_str, FALSE); +} + + +/** + * Start an HTTP call to the API + * + * @param method HTTP method (eg "GET") + * @param body body of request, or NULL if none + * @param max_len maximum number of bytes to return from the request. -1 for + * default (512K). + * + * @returns handle for the request, or NULL if the request couldn't be started + * (eg, invalid hostname). In this case, the error_callback will have + * been called already. + */ +static MatrixApiRequestData *matrix_api_start(const gchar *url, + const gchar *method, const gchar *body, MatrixConnectionData *conn, + MatrixApiCallback callback, MatrixApiErrorCallback error_callback, + MatrixApiBadResponseCallback bad_response_callback, + gpointer user_data, gssize max_len) +{ + MatrixApiRequestData *data; + gchar *request; + PurpleUtilFetchUrlData *purple_data; + + if (error_callback == NULL) + error_callback = matrix_api_error; + if (bad_response_callback == NULL) + bad_response_callback = matrix_api_bad_response; + + /* _build_request assumes the url is absolute, so enforce that here */ + if(!g_str_has_prefix(url, "http://") && + !g_str_has_prefix(url, "https://")) { + gchar *error_msg; + error_msg = g_strdup_printf(_("Invalid homeserver URL %s"), url); + error_callback(conn, user_data, error_msg); + g_free(error_msg); + return NULL; + } + + request = _build_request(conn->pc->account, url, method, body); + + if(purple_debug_is_unsafe()) + purple_debug_info("matrixprpl", "request %s\n", request); + + + data = g_new0(MatrixApiRequestData, 1); + data->conn = conn; + data->callback = callback; + data->error_callback = error_callback; + data->bad_response_callback = bad_response_callback; + data->user_data = user_data; + + purple_data = purple_util_fetch_url_request_len_with_account( + conn -> pc -> account, + url, FALSE, NULL, TRUE, request, TRUE, max_len, matrix_api_complete, + data); + + if(purple_data == NULL) { + /* we couldn't start the request. In this case, our callback will + * already have been called, which will have freed data. + */ + data = NULL; + } else { + data->purple_data = purple_data; + } + + g_free(request); + return data; +} + + +void matrix_api_cancel(MatrixApiRequestData *data) +{ + if(data -> purple_data != NULL) + purple_util_fetch_url_cancel(data -> purple_data); + data -> purple_data = NULL; + (data->error_callback)(data->conn, data->user_data, "cancelled"); + + g_free(data); } + gchar *_build_login_body(const gchar *username, const gchar *password) { JsonObject *body; @@ -496,23 +552,17 @@ MatrixApiRequestData *matrix_api_password_login(MatrixConnectionData *conn, { gchar *url, *json; MatrixApiRequestData *fetch_data; - GString *request; - url = g_strconcat(conn->homeserver, "/_matrix/client/api/v1/login", + purple_debug_info("matrixprpl", "logging in %s\n", username); + + url = g_strconcat(conn->homeserver, "_matrix/client/api/v1/login", NULL); json = _build_login_body(username, password); - request = _build_request(conn->pc->account, url, "POST", json); - g_free(json); - if(purple_debug_is_unsafe()) - purple_debug_info("matrixprpl", "request %s\n", request->str); - else - purple_debug_info("matrixprpl", "logging in %s\n", username); - - fetch_data = matrix_api_start(url, request->str, conn, callback, + fetch_data = matrix_api_start(url, "POST", json, conn, callback, NULL, NULL, user_data, 0); - g_string_free(request, TRUE); + g_free(json); g_free(url); return fetch_data; @@ -528,13 +578,11 @@ MatrixApiRequestData *matrix_api_sync(MatrixConnectionData *conn, { GString *url; MatrixApiRequestData *fetch_data; - GString *request; - url = g_string_new(""); + url = g_string_new(conn->homeserver); g_string_append_printf(url, - "%s/_matrix/client/v2_alpha/sync?access_token=%s&timeout=%i", - conn->homeserver, purple_url_encode(conn->access_token), - timeout); + "_matrix/client/v2_alpha/sync?access_token=%s&timeout=%i", + purple_url_encode(conn->access_token), timeout); if(since != NULL) g_string_append_printf(url, "&since=%s", purple_url_encode(since)); @@ -545,15 +593,12 @@ MatrixApiRequestData *matrix_api_sync(MatrixConnectionData *conn, purple_debug_info("matrixprpl", "syncing %s since %s (full_state=%i)\n", conn->pc->account->username, since, full_state); - request = _build_request(conn->pc->account, url->str, "GET", NULL); - /* XXX: stream the response, so that we don't need to allocate so much * memory? But it's JSON */ - fetch_data = matrix_api_start(url->str, request->str, conn, callback, + fetch_data = matrix_api_start(url->str, "GET", NULL, conn, callback, error_callback, bad_response_callback, user_data, 10*1024*1024); g_string_free(url, TRUE); - g_string_free(request, TRUE); return fetch_data; } @@ -567,7 +612,6 @@ MatrixApiRequestData *matrix_api_send(MatrixConnectionData *conn, { GString *url; MatrixApiRequestData *fetch_data; - GString *request; JsonNode *body_node; JsonGenerator *generator; gchar *json; @@ -575,9 +619,8 @@ MatrixApiRequestData *matrix_api_send(MatrixConnectionData *conn, /* purple_url_encode uses a single static buffer, so we have to build up * the url gradually */ - url = g_string_new(""); - g_string_append_printf(url, "%s/_matrix/client/api/v1/rooms/", - conn->homeserver); + url = g_string_new(conn->homeserver); + g_string_append(url, "_matrix/client/api/v1/rooms/"); g_string_append(url, purple_url_encode(room_id)); g_string_append(url, "/send/"); g_string_append(url, purple_url_encode(event_type)); @@ -595,22 +638,18 @@ MatrixApiRequestData *matrix_api_send(MatrixConnectionData *conn, g_object_unref(G_OBJECT(generator)); json_node_free(body_node); - request = _build_request(conn->pc->account, url->str, "PUT", json); - g_free(json); - purple_debug_info("matrixprpl", "sending %s on %s\n", event_type, room_id); - fetch_data = matrix_api_start(url->str, request->str, conn, callback, + fetch_data = matrix_api_start(url->str, "PUT", json, conn, callback, error_callback, bad_response_callback, user_data, 0); - g_string_free(request, TRUE); + g_free(json); g_string_free(url, TRUE); return fetch_data; } #if 0 -/* if we bring this back, it needs to build its own connection */ MatrixApiRequestData *matrix_api_get_room_state(MatrixConnectionData *conn, const gchar *room_id, MatrixApiCallback callback, |