/* Licensed to Stichting The Commons Conservancy (TCC) under one or more * contributor license agreements. See the AUTHORS file distributed with * this work for additional information regarding copyright ownership. * TCC licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include "signtext.h" #include "crypto.h" #include "message.h" #include "config.h" void message_cancel(SignTextData *signtext) { const char *uuid = adw_view_stack_get_visible_child_name(signtext->stack); SignTextInstance *instance = g_object_get_data(G_OBJECT (signtext->stack), uuid); if (instance) { /* * Send an error to show we cancelled. */ message_send_error(instance, "error:userCancel"); signtext_instance_free(instance); } } /* * After receiving a parsed JSON message read from stdin, we end up here in the main UI * thread. * * Parse the message, and create or update the UI pages in the stacks for each message we * receive. */ static void message_receive_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { SignTextData *signtext = user_data; SignTextInstance *instance; GError *gerror = NULL; JsonParser *parser = NULL; #if 0 g_printerr("message_receive_done\n"); #endif parser = g_task_propagate_pointer (G_TASK (res), &gerror); if (parser) { GUri *uri; JsonObject *message; JsonNode *request; const gchar *url, *uuid; gint64 id; /* parse the message */ JsonNode *root = json_parser_get_root (parser); json_node_seal(root); message = json_node_get_object(root); if (!message) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_MISSING_ROOT, "JSON payload was empty."); goto fatal; } url = json_object_get_string_member(message, "url"); if (!url) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_MISSING_URL, "JSON payload has missing 'url'."); goto fatal; } uri = g_uri_parse(url, G_URI_FLAGS_NONE, &gerror); if (!uri) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_URL_INVALID, "JSON payload has invalid 'url'."); goto fatal; } uuid = json_object_get_string_member(message, "uuid"); if (!uuid) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_MISSING_UUID, "JSON payload has missing 'uuid'."); goto fatal; } id = json_object_get_int_member(message, "id"); if (!id) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_MISSING_ID, "JSON payload has missing/invalid/zero 'id'."); goto fatal; } request = json_object_get_member(message, "request"); if (!request) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_MISSING_REQUEST, "JSON payload has missing 'request'."); goto fatal; } instance = g_object_get_data(G_OBJECT (signtext->stack), uuid); if (instance) { } else { instance = signtext_instance_new(signtext, id, uuid, uri); // instance->box = GTK_LIST_BOX(gtk_list_box_new()); // gtk_widget_set_vexpand (GTK_WIDGET(instance->box), 1); // g_object_set_data_full(G_OBJECT (signtext->stack), instance->uuid, instance, (GDestroyNotify)signtext_instance_free); // adw_view_stack_add_titled(signtext->stack, GTK_WIDGET (instance->page), instance->uuid, g_uri_get_host(instance->uri)); // instance->stack = signtext->stack; } /* * Process request. * * A string request is a data payload, append it to existing text buffer and * send back an ACK. */ if (json_node_get_value_type(request) == G_TYPE_STRING) { GtkTextIter end; gtk_text_buffer_get_end_iter(instance->buffer, &end); gtk_text_buffer_insert(instance->buffer, &end, json_node_get_string(request), -1); /* * A response with a value of TRUE means ACK, send us the next bit of data. */ message_send_boolean_response(instance, TRUE); } /* * An object request means the data is done and we're ready to let the end user * see the UI. * * Make the UI visible. */ else if (json_node_get_node_type(request) == JSON_NODE_OBJECT) { GListStore *certificates; JsonArray *cas = json_object_get_array_member(json_node_get_object(request), "CAs"); if (cas) { guint len = json_array_get_length(cas); guint i; for (i = 0; i < len; i++) { const gchar *ca = json_array_get_string_element(cas, i); crypto_instance_append_ca(instance, ca, &gerror); } } #if 0 /* port to gtk4 */ certificates = crypto_filter(instance, signtext->certificates); #endif certificates = signtext->certificates; crypto_instance_start(instance, certificates); gtk_widget_set_visible (GTK_WIDGET(signtext->window), 1); gtk_window_present(GTK_WINDOW(signtext->window)); gtk_widget_set_visible(GTK_WIDGET(instance->progress), FALSE); } /* * A boolean request is an ack/nak. */ else if (json_node_get_value_type(request) == G_TYPE_BOOLEAN) { if (json_node_get_boolean(request)) { /* ack, send the next bit */ crypto_sign_continue(instance); } else { /* nak, close everything down */ signtext_instance_free(instance); } } /* * Anything else is an error. */ else { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_REQUEST_NOT_RECOGNISED, "JSON payload 'request' is not recognised."); goto fatal; } g_object_unref(parser); } if (gerror) { AdwDialog* dialog; fatal: dialog = adw_alert_dialog_new ("Redwax SignText - Error", NULL); adw_alert_dialog_format_body (ADW_ALERT_DIALOG (dialog), ("Error: %s"), gerror->message); adw_alert_dialog_add_response(ADW_ALERT_DIALOG (dialog), "Ok", "Ok"); adw_dialog_present (dialog, instance->signtext->window); g_error_free (gerror); } } static void message_receive_do (GTask *task, gpointer source_obj, gpointer task_data, GCancellable *cancellable) { JsonParser *parser = NULL; GError *gerror = NULL; gchar *buf = NULL; GInputStream *stream = NULL; guint32 size; gsize count; #if 0 g_printerr("message_receive_do\n"); #endif stream = g_unix_input_stream_new (STDIN_FILENO, FALSE); if (!g_input_stream_read_all(stream, (gchar *)(&size), sizeof(size), &count, cancellable, &gerror)) { g_printerr("message_receive_do: read size failed\n"); goto done; } buf = g_malloc(size); if (!buf) { g_printerr("message_receive_do: allocation failed\n"); goto done; } if (!g_input_stream_read_all(stream, buf, size, &count, cancellable, &gerror)) { g_printerr("message_receive_do: read message failed\n"); goto done; } parser = json_parser_new(); if (!json_parser_load_from_data(parser, buf, size, &gerror)) { //g_main_loop_quit() g_printerr("message_receive_do: parse json failed\n"); // handle error goto done; } #if 0 g_printerr("Payload received: %.*s\n", (int)size, buf); #endif g_task_return_pointer(task, g_object_ref(parser), g_object_unref); done: if (buf) { g_free(buf); } if (parser) { g_object_unref(parser); } if (stream) { g_object_unref(stream); } if (gerror) { g_task_return_error (task, gerror); } return; } /* * In the main UI thread, we want to receive a single JSON message on stdin. * * Spawn a GTask to make this happen in a seperate thread, and return immediately. We * will eventually do the work in message_receive_do() in a sub-thread, and then we will * eventually process the result in message_receive_done() in the UI thread, at which * point we will update our UI and if necessary, handle any errors. */ void message_receive(SignTextData *signtext) { GTask *task = g_task_new (signtext->window, signtext->cancellable, message_receive_done, signtext); g_task_run_in_thread (task, message_receive_do); g_object_unref (task); } static void message_send_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { SignTextData *signtext = user_data; GError *gerror = NULL; #if 0 g_printerr("message_send_done\n"); #endif g_task_propagate_boolean (G_TASK (res), &gerror); if (gerror) { AdwDialog* dialog; dialog = adw_alert_dialog_new ("Redwax SignText - Error", NULL); adw_alert_dialog_format_body (ADW_ALERT_DIALOG (dialog), ("Error: %s"), gerror->message); adw_dialog_present (dialog, signtext->window); g_error_free (gerror); } else { message_receive(signtext); } } static void message_send_do (GTask *task, gpointer source_obj, gpointer task_data, GCancellable *cancellable) { JsonNode *root = task_data; JsonGenerator *generator = NULL; GError *gerror = NULL; gchar *buf = NULL; GOutputStream *stream = NULL; guint32 size; gsize count; #if 0 g_printerr("message_send_do\n"); #endif generator = json_generator_new(); json_generator_set_root(generator, root); stream = g_unix_output_stream_new (STDOUT_FILENO, FALSE); buf = json_generator_to_data(generator, &count); size = count; if (!g_output_stream_write_all(stream, (gchar *)(&size), sizeof(size), &count, cancellable, &gerror)) { g_printerr("message_send_do: write message size failed\n"); goto done; } if (!g_output_stream_write_all(stream, buf, size, &count, cancellable, &gerror)) { g_printerr("message_send_do: write message content failed\n"); goto done; } #if 0 g_printerr("Payload sent: %.*s\n", (int)size, buf); #endif done: if (buf) { g_free(buf); } if (generator) { g_object_unref(generator); } if (stream) { g_object_unref(stream); } if (gerror) { g_task_return_error (task, gerror); } else { g_task_return_boolean(task, TRUE); } return; } /* * In the main UI thread, we want to send a single JSON message on stdout. * * Spawn a GTask to make this happen in a separate thread, and return immediately. We * will eventually do the work of sending the message in message_send_do() in a * sub-thread, and then we will eventually receive either success or an error in the * main thread in message_send_done(). * * At this point the loop is completed by repeating the request to receive the next * single JSON message on stdin. * * The JsonNode is freed at the end of the task. */ static void message_send(SignTextData *signtext, JsonNode *root) { GTask *task = g_task_new (signtext->window, signtext->cancellable, message_send_done, signtext); g_task_set_task_data (task, root, (GDestroyNotify)json_node_free); g_task_run_in_thread (task, message_send_do); g_object_unref (task); } void message_send_error(SignTextInstance *instance, gchar *error) { JsonBuilder *builder; JsonNode *root; builder = json_builder_new_immutable(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "uuid"); json_builder_add_string_value(builder, instance->uuid); json_builder_set_member_name(builder, "id"); json_builder_add_int_value(builder, instance->id); json_builder_set_member_name(builder, "error"); json_builder_add_string_value(builder, "error:userCancel"); json_builder_end_object(builder); root = json_builder_get_root(builder); message_send(instance->signtext, root); g_object_unref(builder); } void message_send_boolean_response(SignTextInstance *instance, gboolean response) { JsonBuilder *builder; JsonNode *root; builder = json_builder_new_immutable(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "uuid"); json_builder_add_string_value(builder, instance->uuid); json_builder_set_member_name(builder, "id"); json_builder_add_int_value(builder, instance->id); json_builder_set_member_name(builder, "response"); json_builder_add_boolean_value(builder, response); json_builder_end_object(builder); root = json_builder_get_root(builder); message_send(instance->signtext, root); g_object_unref(builder); } void message_send_string_response(SignTextInstance *instance, gchar *response) { JsonBuilder *builder; JsonNode *root; builder = json_builder_new_immutable(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "uuid"); json_builder_add_string_value(builder, instance->uuid); json_builder_set_member_name(builder, "id"); json_builder_add_int_value(builder, instance->id); json_builder_set_member_name(builder, "response"); json_builder_add_string_value(builder, response); json_builder_end_object(builder); root = json_builder_get_root(builder); message_send(instance->signtext, root); g_object_unref(builder); }