anastasis-gtk_handle-secret-buttons.c (14646B)
1 /* 2 This file is part of anastasis-gtk. 3 Copyright (C) 2021 Anastasis SARL 4 5 Anastasis is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 Anastasis is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with Anastasis; see the file COPYING. If not, write to the 17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 Boston, MA 02110-1301, USA. 19 */ 20 /** 21 * @file src/anastasis/anastasis-gtk_handle-secret-buttons.c 22 * @brief Main function of anastasis-gtk 23 * @author Christian Grothoff 24 * @author Dennis Neufeld 25 */ 26 #include <gnunet/gnunet_util_lib.h> 27 #include "anastasis_gtk_util.h" 28 #include "anastasis-gtk_action.h" 29 #include "anastasis-gtk_handle-expiration-change.h" 30 #include "anastasis-gtk_helper.h" 31 #include <jansson.h> 32 #include <magic.h> 33 34 35 /** 36 * Global handle to MAGIC data. 37 */ 38 static magic_t magic; 39 40 41 /** 42 * Function called once the user picked the file holding the secret. 43 * 44 * @param source the file dialog 45 * @param res the result of the user's choice 46 * @param user_data unused 47 */ 48 static void 49 open_secret_ready_cb (GObject *source, 50 GAsyncResult *res, 51 gpointer user_data) 52 { 53 GFile *file; 54 char *filename; 55 const char *fn; 56 size_t data_size; 57 void *data; 58 const char *mime; 59 GtkEntry *entry; 60 const char *name; 61 62 (void) user_data; 63 file = gtk_file_dialog_open_finish (GTK_FILE_DIALOG (source), 64 res, 65 NULL); 66 if (NULL == file) 67 return; /* user aborted */ 68 filename = ANASTASIS_GTK_file_get_path_utf8 (file); 69 g_object_unref (file); 70 if (NULL == filename) 71 return; 72 fn = strrchr (filename, 73 '/'); 74 if (NULL == fn) 75 fn = filename; 76 else 77 fn++; /* skip '/' itself */ 78 { 79 struct GNUNET_DISK_FileHandle *fh; 80 off_t size; 81 enum GNUNET_GenericReturnValue ret; 82 83 fh = GNUNET_DISK_file_open (filename, 84 GNUNET_DISK_OPEN_READ, 85 GNUNET_DISK_PERM_NONE); 86 if (NULL == fh) 87 { 88 AG_error ("Failed to open file `%s': %s", 89 filename, 90 strerror (errno)); 91 GNUNET_free (filename); 92 return; 93 } 94 ret = GNUNET_DISK_file_handle_size (fh, 95 &size); 96 if (GNUNET_OK != ret) 97 { 98 AG_error ("Failed to obtain file size `%s': %s", 99 filename, 100 strerror (errno)); 101 GNUNET_free (filename); 102 GNUNET_DISK_file_close (fh); 103 return; 104 } 105 data_size = (size_t) size; 106 data = GNUNET_malloc_large (data_size); 107 if (NULL == data) 108 { 109 AG_error ("Failed to allocate memory for file `%s': %s", 110 filename, 111 strerror (errno)); 112 GNUNET_free (filename); 113 GNUNET_DISK_file_close (fh); 114 return; 115 } 116 if (size != 117 GNUNET_DISK_file_read (fh, 118 data, 119 data_size)) 120 { 121 AG_error ("Failed read file `%s': %s", 122 filename, 123 strerror (errno)); 124 GNUNET_free (data); 125 GNUNET_free (filename); 126 GNUNET_DISK_file_close (fh); 127 return; 128 } 129 GNUNET_DISK_file_close (fh); 130 } 131 entry = GTK_ENTRY (GCG_get_main_window_object ( 132 "anastasis_gtk_secret_name_entry")); 133 name = gtk_editable_get_text (GTK_EDITABLE (entry)); 134 mime = magic_buffer (magic, 135 data, 136 data_size); 137 { 138 json_t *arguments; 139 struct GNUNET_TIME_Timestamp expiration; 140 141 expiration = AG_get_desired_expiration (); 142 if (GNUNET_TIME_absolute_is_zero (expiration.abs_time)) 143 { 144 GNUNET_free (data); 145 GNUNET_free (filename); 146 return; /* failured */ 147 } 148 arguments = json_pack ("{s:s?,s:{s:o,s:s,s:s?},s:o}", 149 "name", 150 name, 151 "secret", 152 "value", 153 GNUNET_JSON_from_data (data, 154 data_size), 155 "filename", 156 fn, 157 "mime", 158 mime, 159 "expiration", 160 GNUNET_JSON_from_timestamp (expiration)); 161 GNUNET_free (filename); 162 GNUNET_free (data); 163 GNUNET_assert (NULL != arguments); 164 AG_freeze (); 165 AG_ra = ANASTASIS_redux_action (AG_redux_state, 166 "enter_secret", 167 arguments, 168 &AG_action_cb, 169 NULL); 170 json_decref (arguments); 171 } 172 } 173 174 175 /** 176 * User clicked the "open" button in the dialog where the secret is entered. 177 * 178 * @param button the button 179 * @param user_data unused 180 */ 181 void 182 anastasis_gtk_enter_secret_open_button_clicked_cb (GtkButton *button, 183 gpointer user_data) 184 { 185 GtkFileDialog *dialog; 186 187 (void) user_data; 188 dialog = gtk_file_dialog_new (); 189 gtk_file_dialog_set_title (dialog, 190 _ ("Open file with secret to back up")); 191 gtk_file_dialog_open (dialog, 192 GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (button))), 193 NULL, 194 &open_secret_ready_cb, 195 NULL); 196 g_object_unref (dialog); 197 } 198 199 200 /** 201 * Function called once the user picked the file to save the secret in. 202 * 203 * @param source the file dialog 204 * @param res the result of the user's choice 205 * @param user_data unused 206 */ 207 static void 208 save_secret_ready_cb (GObject *source, 209 GAsyncResult *res, 210 gpointer user_data) 211 { 212 GFile *file; 213 char *filename; 214 size_t data_len = 0; 215 const char *text = NULL; 216 void *data = NULL; 217 json_t *cs; 218 struct GNUNET_JSON_Specification cspec[] = { 219 GNUNET_JSON_spec_mark_optional ( 220 GNUNET_JSON_spec_string ("text", 221 &text), 222 NULL), 223 GNUNET_JSON_spec_mark_optional ( 224 GNUNET_JSON_spec_varsize ("value", 225 &data, 226 &data_len), 227 NULL), 228 GNUNET_JSON_spec_end () 229 }; 230 231 (void) user_data; 232 file = gtk_file_dialog_save_finish (GTK_FILE_DIALOG (source), 233 res, 234 NULL); 235 if (NULL == file) 236 return; /* user aborted */ 237 filename = ANASTASIS_GTK_file_get_path_utf8 (file); 238 g_object_unref (file); 239 if (NULL == filename) 240 return; 241 cs = json_object_get (AG_redux_state, 242 "core_secret"); 243 GNUNET_assert (NULL != cs); 244 if (GNUNET_OK != 245 GNUNET_JSON_parse (cs, 246 cspec, 247 NULL, NULL)) 248 { 249 GNUNET_break (0); 250 return; 251 } 252 { 253 enum GNUNET_GenericReturnValue ret; 254 255 ret = GNUNET_DISK_fn_write (filename, 256 (NULL == data) 257 ? text 258 : data, 259 (NULL == data) 260 ? strlen (text) 261 : data_len, 262 GNUNET_DISK_PERM_USER_READ); 263 switch (ret) 264 { 265 case GNUNET_OK: 266 break; 267 case GNUNET_NO: 268 AG_error ("File `%s' exists", 269 filename); 270 break; 271 case GNUNET_SYSERR: 272 AG_error ("Writing to file `%s' failed: %s", 273 filename, 274 strerror (errno)); 275 break; 276 } 277 } 278 GNUNET_JSON_parse_free (cspec); 279 GNUNET_free (filename); 280 } 281 282 283 /** 284 * User clicked the "save as" button in the dialog with the recovered secret. 285 * 286 * @param button the button 287 * @param user_data unused 288 */ 289 void 290 anastasis_gtk_secret_save_as_button_clicked_cb (GtkButton *button, 291 gpointer user_data) 292 { 293 static const struct 294 { 295 const char *mime; 296 const char *fn; 297 } mime_map [] = { 298 { .mime = "text/plain", 299 .fn = "untitled.txt" }, 300 { .mime = "text/html", 301 .fn = "untitled.html" }, 302 { .mime = "text/xml", 303 .fn = "untitled.xml" }, 304 { .mime = "text/csv", 305 .fn = "untitled.csv" }, 306 { .mime = "image/jpeg", 307 .fn = "untitled.jpeg" }, 308 { .mime = "image/png", 309 .fn = "untitled.png" }, 310 { .mime = "application/pgp-keys", 311 .fn = "untitled.pgp" }, 312 { .mime = "application/json", 313 .fn = "untitled.json" }, 314 { .mime = "application/taler-wallet-secret", 315 .fn = "untitled.tws" }, 316 { .mime = "application/taler-wallet", 317 .fn = "untitled.twd" }, 318 { .mime = NULL, 319 .fn = NULL } 320 }; 321 322 GtkFileDialog *dialog; 323 const char *mime = NULL; 324 const char *fn = NULL; 325 json_t *cs; 326 struct GNUNET_JSON_Specification spec[] = { 327 GNUNET_JSON_spec_mark_optional ( 328 GNUNET_JSON_spec_string ("filename", 329 &fn), 330 NULL), 331 GNUNET_JSON_spec_mark_optional ( 332 GNUNET_JSON_spec_string ("mime", 333 &mime), 334 NULL), 335 GNUNET_JSON_spec_end () 336 }; 337 338 (void) user_data; 339 cs = json_object_get (AG_redux_state, 340 "core_secret"); 341 GNUNET_assert (NULL != cs); 342 GNUNET_assert (GNUNET_OK == 343 GNUNET_JSON_parse (cs, 344 spec, 345 NULL, NULL)); 346 if ( (NULL == fn) && 347 (NULL != mime) ) 348 { 349 fn = "untitled.secret"; 350 for (unsigned int i = 0; NULL != mime_map[i].mime; i++) 351 { 352 if (0 != strcmp (mime_map[i].mime, 353 mime)) 354 continue; 355 fn = mime_map[i].fn; 356 break; 357 } 358 } 359 dialog = gtk_file_dialog_new (); 360 gtk_file_dialog_set_title (dialog, 361 _ ("Save recovered secret")); 362 { 363 GtkFileFilter *filter; 364 GListStore *filters; 365 366 filter = gtk_file_filter_new (); 367 gtk_file_filter_set_name (filter, 368 _ ("Secrets")); 369 gtk_file_filter_add_pattern (filter, 370 "*.secret"); 371 filters = g_list_store_new (GTK_TYPE_FILE_FILTER); 372 g_list_store_append (filters, 373 filter); 374 g_object_unref (filter); 375 gtk_file_dialog_set_filters (dialog, 376 G_LIST_MODEL (filters)); 377 g_object_unref (filters); 378 } 379 if (NULL != fn) 380 gtk_file_dialog_set_initial_name (dialog, 381 fn); 382 gtk_file_dialog_save (dialog, 383 GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (button))), 384 NULL, 385 &save_secret_ready_cb, 386 NULL); 387 g_object_unref (dialog); 388 } 389 390 391 /** 392 * User clicked the "copy" button in the dialog with the recovered secret. 393 * 394 * @param button the button 395 * @param user_data unused 396 */ 397 void 398 anastasis_gtk_secret_copy_button_clicked_cb (GtkButton *button, 399 gpointer user_data) 400 { 401 size_t data_len = 0; 402 void *data = NULL; 403 const char *mime = NULL; 404 const char *text = NULL; 405 json_t *cs; 406 struct GNUNET_JSON_Specification spec[] = { 407 GNUNET_JSON_spec_mark_optional ( 408 GNUNET_JSON_spec_varsize ("value", 409 &data, 410 &data_len), 411 NULL), 412 GNUNET_JSON_spec_mark_optional ( 413 GNUNET_JSON_spec_string ("mime", 414 &mime), 415 NULL), 416 GNUNET_JSON_spec_mark_optional ( 417 GNUNET_JSON_spec_string ("text", 418 &text), 419 NULL), 420 GNUNET_JSON_spec_end () 421 }; 422 GdkClipboard *cb; 423 424 (void) user_data; 425 cs = json_object_get (AG_redux_state, 426 "core_secret"); 427 GNUNET_assert (NULL != cs); 428 GNUNET_assert (GNUNET_OK == 429 GNUNET_JSON_parse (cs, 430 spec, 431 NULL, NULL)); 432 cb = gtk_widget_get_clipboard (GTK_WIDGET (button)); 433 GNUNET_assert (NULL != cb); 434 if (NULL != text) 435 { 436 gdk_clipboard_set_text (cb, 437 text); 438 } 439 else 440 { 441 if (0 == strncasecmp (mime, 442 "text/", 443 strlen ("text/"))) 444 { 445 char *str; 446 447 /* the secret is not necessarily 0-terminated */ 448 str = g_strndup (data, 449 data_len); 450 gdk_clipboard_set_text (cb, 451 str); 452 g_free (str); 453 } 454 else if (0 == strncasecmp (mime, 455 "image/", 456 strlen ("image/"))) 457 { 458 GdkPixbufLoader *loader; 459 460 loader = gdk_pixbuf_loader_new_with_mime_type (mime, 461 NULL); 462 if (NULL != loader) 463 { 464 GdkPixbuf *pb; 465 466 gdk_pixbuf_loader_write (loader, 467 data, 468 data_len, 469 NULL); 470 pb = gdk_pixbuf_loader_get_pixbuf (loader); 471 if (NULL != pb) 472 { 473 GdkTexture *texture; 474 475 texture = gdk_texture_new_for_pixbuf (pb); 476 gdk_clipboard_set_texture (cb, 477 texture); 478 g_object_unref (texture); 479 } 480 else 481 { 482 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 483 "Failed to parse secret image data.\n"); 484 } 485 g_object_unref (loader); 486 } 487 else 488 { 489 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 490 "Unsupported image mime type `%s'\n", 491 mime); 492 } 493 } 494 else 495 { 496 GNUNET_break (0); 497 } 498 } 499 GNUNET_JSON_parse_free (spec); 500 } 501 502 503 /** 504 * Constructor for the library. Loads the magic file. 505 */ 506 void __attribute__ ((constructor)) 507 mime_ltdl_init () 508 { 509 magic = magic_open (MAGIC_MIME_TYPE); 510 if (0 != magic_load (magic, 511 NULL)) 512 { 513 GNUNET_break (0); 514 } 515 } 516 517 518 /** 519 * Destructor for the library, cleans up. 520 */ 521 void __attribute__ ((destructor)) 522 mime_ltdl_fini () 523 { 524 if (NULL != magic) 525 { 526 magic_close (magic); 527 magic = NULL; 528 } 529 }