1 /*
2 * Seahorse
3 *
4 * Copyright (C) 2008 Stefan Walter
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see
17 * <http://www.gnu.org/licenses/>.
18 */
19
20 #include "config.h"
21
22 #include "seahorse-pgp-key.h"
23 #include "seahorse-pgp-uid.h"
24 #include "seahorse-pgp-signature.h"
25
26 #include <string.h>
27
28 #include <glib/gi18n.h>
29
30 enum {
31 PROP_0,
32 PROP_PARENT,
33 PROP_SIGNATURES,
34 PROP_VALIDITY,
35 PROP_NAME,
36 PROP_EMAIL,
37 PROP_COMMENT
38 };
39
40 typedef struct _SeahorsePgpUidPrivate {
41 SeahorsePgpKey *parent;
42 GListModel *signatures;
43 SeahorseValidity validity;
44 gboolean realized;
45 char *name;
46 char *email;
47 char *comment;
48 } SeahorsePgpUidPrivate;
49
50 G_DEFINE_TYPE_WITH_PRIVATE (SeahorsePgpUid, seahorse_pgp_uid, SEAHORSE_TYPE_OBJECT);
51
52 /* -----------------------------------------------------------------------------
53 * INTERNAL HELPERS
54 */
55
56 static char *
convert_string(const char * str)57 convert_string (const char *str)
58 {
59 if (!str)
60 return NULL;
61
62 /* If not utf8 valid, assume latin 1 */
63 if (!g_utf8_validate (str, -1, NULL))
64 return g_convert (str, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL);
65
66 return g_strdup (str);
67 }
68
69 #ifndef HAVE_STRSEP
70 /* code taken from glibc-2.2.1/sysdeps/generic/strsep.c */
71 char *
strsep(char ** stringp,const char * delim)72 strsep (char **stringp, const char *delim)
73 {
74 char *begin, *end;
75
76 begin = *stringp;
77 if (begin == NULL)
78 return NULL;
79
80 /* A frequent case is when the delimiter string contains only one
81 character. Here we don't need to call the expensive `strpbrk'
82 function and instead work using `strchr'. */
83 if (delim[0] == '\0' || delim[1] == '\0') {
84 char ch = delim[0];
85
86 if (ch == '\0')
87 end = NULL;
88 else {
89 if (*begin == ch)
90 end = begin;
91 else if (*begin == '\0')
92 end = NULL;
93 else
94 end = strchr (begin + 1, ch);
95 }
96 } else
97 /* Find the end of the token. */
98 end = strpbrk (begin, delim);
99
100 if (end) {
101 /* Terminate the token and set *STRINGP past NUL character. */
102 *end++ = '\0';
103 *stringp = end;
104 } else
105 /* No more delimiters; this is the last token. */
106 *stringp = NULL;
107
108 return begin;
109 }
110 #endif /*HAVE_STRSEP*/
111
112 /* Copied from GPGME */
113 static void
parse_user_id(const char * uid,char ** name,char ** email,char ** comment)114 parse_user_id (const char *uid, char **name, char **email, char **comment)
115 {
116 char *src, *tail;
117 g_autofree char *x = NULL;
118 int in_name = 0;
119 int in_email = 0;
120 int in_comment = 0;
121
122 x = tail = src = g_strdup (uid);
123
124 while (*src) {
125 if (in_email) {
126 if (*src == '<')
127 /* Not legal but anyway. */
128 in_email++;
129 else if (*src == '>') {
130 if (!--in_email && !*email) {
131 *email = tail;
132 *src = 0;
133 tail = src + 1;
134 }
135 }
136 } else if (in_comment) {
137 if (*src == '(')
138 in_comment++;
139 else if (*src == ')') {
140 if (!--in_comment && !*comment) {
141 *comment = tail;
142 *src = 0;
143 tail = src + 1;
144 }
145 }
146 } else if (*src == '<') {
147 if (in_name) {
148 if (!*name) {
149 *name = tail;
150 *src = 0;
151 tail = src + 1;
152 }
153 in_name = 0;
154 } else
155 tail = src + 1;
156
157 in_email = 1;
158 } else if (*src == '(') {
159 if (in_name) {
160 if (!*name) {
161 *name = tail;
162 *src = 0;
163 tail = src + 1;
164 }
165 in_name = 0;
166 }
167 in_comment = 1;
168 } else if (!in_name && *src != ' ' && *src != '\t') {
169 in_name = 1;
170 }
171 src++;
172 }
173
174 if (in_name) {
175 if (!*name) {
176 *name = tail;
177 *src = 0;
178 tail = src + 1;
179 }
180 }
181
182 /* Let unused parts point to an EOS. */
183 *name = g_strdup (*name ? *name : "");
184 *email = g_strdup (*email ? *email : "");
185 *comment = g_strdup (*comment ? *comment : "");
186
187 g_strstrip (*name);
188 g_strstrip (*email);
189 g_strstrip (*comment);
190 }
191
192
193 /* -----------------------------------------------------------------------------
194 * OBJECT
195 */
196
197 void
seahorse_pgp_uid_realize(SeahorsePgpUid * self)198 seahorse_pgp_uid_realize (SeahorsePgpUid *self)
199 {
200 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
201 g_autofree char *markup = NULL;
202 g_autofree char *label = NULL;
203
204 /* Don't realize if no name present */
205 if (!priv->name)
206 return;
207
208 priv->realized = TRUE;
209
210 label = seahorse_pgp_uid_calc_label (priv->name, priv->email, priv->comment);
211 markup = seahorse_pgp_uid_calc_markup (priv->name, priv->email, priv->comment, 0);
212 g_object_set (self, "markup", markup, "label", label, NULL);
213 }
214
215 static void
seahorse_pgp_uid_init(SeahorsePgpUid * self)216 seahorse_pgp_uid_init (SeahorsePgpUid *self)
217 {
218 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
219
220 priv->signatures = G_LIST_MODEL (g_list_store_new (SEAHORSE_PGP_TYPE_SIGNATURE));
221 g_object_set (self, "icon", NULL, "usage", SEAHORSE_USAGE_IDENTITY, NULL);
222 }
223
224 static void
seahorse_pgp_uid_constructed(GObject * object)225 seahorse_pgp_uid_constructed (GObject *object)
226 {
227 G_OBJECT_CLASS (seahorse_pgp_uid_parent_class)->constructed (object);
228 seahorse_pgp_uid_realize (SEAHORSE_PGP_UID (object));
229 }
230
231 static void
seahorse_pgp_uid_get_property(GObject * object,unsigned int prop_id,GValue * value,GParamSpec * pspec)232 seahorse_pgp_uid_get_property (GObject *object,
233 unsigned int prop_id,
234 GValue *value,
235 GParamSpec *pspec)
236 {
237 SeahorsePgpUid *self = SEAHORSE_PGP_UID (object);
238
239 switch (prop_id) {
240 case PROP_SIGNATURES:
241 g_value_set_object (value, seahorse_pgp_uid_get_signatures (self));
242 break;
243 case PROP_PARENT:
244 g_value_set_object (value, seahorse_pgp_uid_get_parent (self));
245 break;
246 case PROP_VALIDITY:
247 g_value_set_uint (value, seahorse_pgp_uid_get_validity (self));
248 break;
249 case PROP_NAME:
250 g_value_set_string (value, seahorse_pgp_uid_get_name (self));
251 break;
252 case PROP_EMAIL:
253 g_value_set_string (value, seahorse_pgp_uid_get_email (self));
254 break;
255 case PROP_COMMENT:
256 g_value_set_string (value, seahorse_pgp_uid_get_comment (self));
257 break;
258 }
259 }
260
261 static void
seahorse_pgp_uid_set_property(GObject * object,unsigned int prop_id,const GValue * value,GParamSpec * pspec)262 seahorse_pgp_uid_set_property (GObject *object,
263 unsigned int prop_id,
264 const GValue *value,
265 GParamSpec *pspec)
266 {
267 SeahorsePgpUid *self = SEAHORSE_PGP_UID (object);
268 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
269
270 switch (prop_id) {
271 case PROP_PARENT:
272 g_return_if_fail (priv->parent == NULL);
273 priv->parent = g_value_get_object (value);
274 break;
275 case PROP_VALIDITY:
276 seahorse_pgp_uid_set_validity (self, g_value_get_uint (value));
277 break;
278 case PROP_NAME:
279 seahorse_pgp_uid_set_name (self, g_value_get_string (value));
280 break;
281 case PROP_EMAIL:
282 seahorse_pgp_uid_set_email (self, g_value_get_string (value));
283 break;
284 case PROP_COMMENT:
285 seahorse_pgp_uid_set_comment (self, g_value_get_string (value));
286 break;
287 }
288 }
289
290 static void
seahorse_pgp_uid_object_finalize(GObject * gobject)291 seahorse_pgp_uid_object_finalize (GObject *gobject)
292 {
293 SeahorsePgpUid *self = SEAHORSE_PGP_UID (gobject);
294 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
295
296 g_clear_object (&priv->signatures);
297 g_clear_pointer (&priv->name, g_free);
298 g_clear_pointer (&priv->email, g_free);
299 g_clear_pointer (&priv->comment, g_free);
300
301 G_OBJECT_CLASS (seahorse_pgp_uid_parent_class)->finalize (gobject);
302 }
303
304 static void
seahorse_pgp_uid_class_init(SeahorsePgpUidClass * klass)305 seahorse_pgp_uid_class_init (SeahorsePgpUidClass *klass)
306 {
307 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
308
309 gobject_class->constructed = seahorse_pgp_uid_constructed;
310 gobject_class->finalize = seahorse_pgp_uid_object_finalize;
311 gobject_class->set_property = seahorse_pgp_uid_set_property;
312 gobject_class->get_property = seahorse_pgp_uid_get_property;
313
314 g_object_class_install_property (gobject_class, PROP_VALIDITY,
315 g_param_spec_uint ("validity", "Validity", "Validity of this identity",
316 0, G_MAXUINT, 0,
317 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
318
319 g_object_class_install_property (gobject_class, PROP_PARENT,
320 g_param_spec_object ("parent", "Parent Key", "Parent Key",
321 SEAHORSE_PGP_TYPE_KEY,
322 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
323
324 g_object_class_install_property (gobject_class, PROP_NAME,
325 g_param_spec_string ("name", "Name", "User ID name",
326 "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
327
328 g_object_class_install_property (gobject_class, PROP_EMAIL,
329 g_param_spec_string ("email", "Email", "User ID email",
330 "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
331
332 g_object_class_install_property (gobject_class, PROP_COMMENT,
333 g_param_spec_string ("comment", "Comment", "User ID comment",
334 "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
335
336 g_object_class_install_property (gobject_class, PROP_SIGNATURES,
337 g_param_spec_object ("signatures", "Signatures", "Signatures on this UID",
338 G_TYPE_LIST_MODEL,
339 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
340 }
341
342 /* -----------------------------------------------------------------------------
343 * PUBLIC
344 */
345
346 SeahorsePgpUid *
seahorse_pgp_uid_new(SeahorsePgpKey * parent,const char * uid_string)347 seahorse_pgp_uid_new (SeahorsePgpKey *parent,
348 const char *uid_string)
349 {
350 g_autofree char *name = NULL;
351 g_autofree char *email = NULL;
352 g_autofree char *comment = NULL;
353
354 g_return_val_if_fail (SEAHORSE_PGP_IS_KEY (parent), NULL);
355
356 if (uid_string)
357 parse_user_id (uid_string, &name, &email, &comment);
358
359 return g_object_new (SEAHORSE_PGP_TYPE_UID,
360 "parent", parent,
361 "name", name,
362 "email", email,
363 "comment", comment,
364 NULL);
365 }
366
367 /**
368 * seahorse_pgp_uid_get_signatures:
369 * @self: A uid
370 *
371 * Returns: (transfer none): The PGP key this UID belongs to
372 */
373 SeahorsePgpKey *
seahorse_pgp_uid_get_parent(SeahorsePgpUid * self)374 seahorse_pgp_uid_get_parent (SeahorsePgpUid *self)
375 {
376 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
377
378 g_return_val_if_fail (SEAHORSE_PGP_IS_UID (self), NULL);
379 return priv->parent;
380 }
381
382 /**
383 * seahorse_pgp_uid_get_signatures:
384 * @self: A uid
385 *
386 * Returns: (transfer none): The list of #SeahorsePgpSignatures
387 */
388 GListModel *
seahorse_pgp_uid_get_signatures(SeahorsePgpUid * self)389 seahorse_pgp_uid_get_signatures (SeahorsePgpUid *self)
390 {
391 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
392
393 g_return_val_if_fail (SEAHORSE_PGP_IS_UID (self), NULL);
394 return priv->signatures;
395 }
396
397 void
seahorse_pgp_uid_add_signature(SeahorsePgpUid * self,SeahorsePgpSignature * signature)398 seahorse_pgp_uid_add_signature (SeahorsePgpUid *self,
399 SeahorsePgpSignature *signature)
400 {
401 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
402 const char *keyid;
403
404 g_return_if_fail (SEAHORSE_PGP_IS_UID (self));
405 g_return_if_fail (SEAHORSE_PGP_IS_SIGNATURE (signature));
406
407 keyid = seahorse_pgp_signature_get_keyid (signature);
408
409 /* Don't add signature of the parent key */
410 if (seahorse_pgp_key_has_keyid (priv->parent, keyid))
411 return;
412
413 /* Don't allow duplicates */
414 for (unsigned i = 0; i < g_list_model_get_n_items (priv->signatures); i++) {
415 g_autoptr(SeahorsePgpSignature) sig = g_list_model_get_item (priv->signatures, i);
416 const char *sig_keyid;
417
418 sig = g_list_model_get_item (priv->signatures, i);
419 sig_keyid = seahorse_pgp_signature_get_keyid (sig);
420 if (seahorse_pgp_keyid_equal (keyid, sig_keyid))
421 return;
422 }
423
424 g_list_store_append (G_LIST_STORE (priv->signatures), signature);
425 }
426
427 void
seahorse_pgp_uid_remove_signature(SeahorsePgpUid * self,SeahorsePgpSignature * signature)428 seahorse_pgp_uid_remove_signature (SeahorsePgpUid *self,
429 SeahorsePgpSignature *signature)
430 {
431 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
432
433 g_return_if_fail (SEAHORSE_PGP_IS_UID (self));
434 g_return_if_fail (SEAHORSE_PGP_IS_SIGNATURE (signature));
435
436 for (unsigned i = 0; i < g_list_model_get_n_items (priv->signatures); i++) {
437 g_autoptr(SeahorsePgpSignature) sig = NULL;
438
439 sig = g_list_model_get_item (priv->signatures, i);
440 if (signature == sig) {
441 g_list_store_remove (G_LIST_STORE (priv->signatures), i);
442 break;
443 }
444 }
445 }
446
447 SeahorseValidity
seahorse_pgp_uid_get_validity(SeahorsePgpUid * self)448 seahorse_pgp_uid_get_validity (SeahorsePgpUid *self)
449 {
450 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
451
452 g_return_val_if_fail (SEAHORSE_PGP_IS_UID (self), SEAHORSE_VALIDITY_UNKNOWN);
453 return priv->validity;
454 }
455
456 void
seahorse_pgp_uid_set_validity(SeahorsePgpUid * self,SeahorseValidity validity)457 seahorse_pgp_uid_set_validity (SeahorsePgpUid *self, SeahorseValidity validity)
458 {
459 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
460
461 g_return_if_fail (SEAHORSE_PGP_IS_UID (self));
462 priv->validity = validity;
463 g_object_notify (G_OBJECT (self), "validity");
464 }
465
466 /**
467 * seahorse_pgp_uid_get_name:
468 * @self: A uid
469 *
470 * Returns: (transfer none): The name part of the UID
471 */
472 const char *
seahorse_pgp_uid_get_name(SeahorsePgpUid * self)473 seahorse_pgp_uid_get_name (SeahorsePgpUid *self)
474 {
475 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
476
477 g_return_val_if_fail (SEAHORSE_PGP_IS_UID (self), NULL);
478 if (!priv->name)
479 priv->name = g_strdup ("");
480 return priv->name;
481 }
482
483 void
seahorse_pgp_uid_set_name(SeahorsePgpUid * self,const char * name)484 seahorse_pgp_uid_set_name (SeahorsePgpUid *self, const char *name)
485 {
486 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
487 GObject *obj;
488
489 g_return_if_fail (SEAHORSE_PGP_IS_UID (self));
490
491 g_free (priv->name);
492 priv->name = convert_string (name);
493
494 obj = G_OBJECT (self);
495 g_object_freeze_notify (obj);
496 if (!priv->realized)
497 seahorse_pgp_uid_realize (self);
498 g_object_notify (obj, "name");
499 g_object_thaw_notify (obj);
500 }
501
502 /**
503 * seahorse_pgp_uid_get_email:
504 * @self: A uid
505 *
506 * Returns: (transfer none): The email part of the UID (if empty, returns "")
507 */
508 const char *
seahorse_pgp_uid_get_email(SeahorsePgpUid * self)509 seahorse_pgp_uid_get_email (SeahorsePgpUid *self)
510 {
511 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
512
513 g_return_val_if_fail (SEAHORSE_PGP_IS_UID (self), NULL);
514 if (!priv->email)
515 priv->email = g_strdup ("");
516 return priv->email;
517 }
518
519 void
seahorse_pgp_uid_set_email(SeahorsePgpUid * self,const char * email)520 seahorse_pgp_uid_set_email (SeahorsePgpUid *self, const char *email)
521 {
522 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
523 GObject *obj = G_OBJECT (self);
524
525 g_return_if_fail (SEAHORSE_PGP_IS_UID (self));
526
527 g_free (priv->email);
528 priv->email = convert_string (email);
529
530 g_object_freeze_notify (obj);
531 if (!priv->realized)
532 seahorse_pgp_uid_realize (self);
533 g_object_notify (obj, "email");
534 g_object_thaw_notify (obj);
535 }
536
537 /**
538 * seahorse_pgp_uid_get_comment:
539 * @self: A uid
540 *
541 * Returns: (transfer none): The comment part of the UID (if empty, returns "")
542 */
543 const char *
seahorse_pgp_uid_get_comment(SeahorsePgpUid * self)544 seahorse_pgp_uid_get_comment (SeahorsePgpUid *self)
545 {
546 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
547
548 g_return_val_if_fail (SEAHORSE_PGP_IS_UID (self), NULL);
549 if (!priv->comment)
550 priv->comment = g_strdup ("");
551 return priv->comment;
552 }
553
554 void
seahorse_pgp_uid_set_comment(SeahorsePgpUid * self,const char * comment)555 seahorse_pgp_uid_set_comment (SeahorsePgpUid *self, const char *comment)
556 {
557 SeahorsePgpUidPrivate *priv = seahorse_pgp_uid_get_instance_private (self);
558 GObject *obj = G_OBJECT (self);
559
560 g_return_if_fail (SEAHORSE_PGP_IS_UID (self));
561
562 g_free (priv->comment);
563 priv->comment = convert_string (comment);
564
565 g_object_freeze_notify (obj);
566 if (!priv->realized)
567 seahorse_pgp_uid_realize (self);
568 g_object_notify (obj, "comment");
569 g_object_thaw_notify (obj);
570 }
571
572 /**
573 * seahorse_pgp_uid_calc_label:
574 * @name:
575 * @email: (nullable):
576 * @command: (nullable):
577 *
578 * Builds a PGP UID from the name (and if available) name and comment in the
579 * form of "name (comment) <email>"
580 *
581 * Returns: (transfer full): The PGP UID string
582 */
583 char *
seahorse_pgp_uid_calc_label(const char * name,const char * email,const char * comment)584 seahorse_pgp_uid_calc_label (const char *name,
585 const char *email,
586 const char *comment)
587 {
588 GString *string;
589
590 g_return_val_if_fail (name, NULL);
591
592 string = g_string_new ("");
593 g_string_append (string, name);
594
595 if (email && email[0]) {
596 g_string_append (string, " <");
597 g_string_append (string, email);
598 g_string_append (string, ">");
599 }
600
601 if (comment && comment[0]) {
602 g_string_append (string, " (");
603 g_string_append (string, comment);
604 g_string_append (string, ")");
605 }
606
607 return g_string_free (string, FALSE);
608 }
609
610 char *
seahorse_pgp_uid_calc_markup(const char * name,const char * email,const char * comment,unsigned int flags)611 seahorse_pgp_uid_calc_markup (const char *name,
612 const char *email,
613 const char *comment,
614 unsigned int flags)
615 {
616 const char *format;
617 gboolean strike = FALSE;
618 gboolean grayed = FALSE;
619
620 g_return_val_if_fail (name, NULL);
621
622 if (flags & SEAHORSE_FLAG_EXPIRED || flags & SEAHORSE_FLAG_REVOKED ||
623 flags & SEAHORSE_FLAG_DISABLED)
624 strike = TRUE;
625 if (!(flags & SEAHORSE_FLAG_TRUSTED))
626 grayed = TRUE;
627
628 if (strike && grayed)
629 format = "<span strikethrough='true' foreground='#555555'>%s<span size='small' rise='0'>%s%s%s%s%s</span></span>";
630 else if (grayed)
631 format = "<span foreground='#555555'>%s<span size='small' rise='0'>%s%s%s%s%s</span></span>";
632 else if (strike)
633 format = "<span strikethrough='true'>%s<span foreground='#555555' size='small' rise='0'>%s%s%s%s%s</span></span>";
634 else
635 format = "%s<span foreground='#555555' size='small' rise='0'>%s%s%s%s%s</span>";
636
637 return g_markup_printf_escaped (format, name,
638 email && email[0] ? " " : "",
639 email && email[0] ? email : "",
640 comment && comment[0] ? " '" : "",
641 comment && comment[0] ? comment : "",
642 comment && comment[0] ? "'" : "");
643 }
644