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