diff options
| author | Marcus Brinkmann <[email protected]> | 2005-10-02 14:39:31 +0000 | 
|---|---|---|
| committer | Marcus Brinkmann <[email protected]> | 2005-10-02 14:39:31 +0000 | 
| commit | 5f5faeafa1e17663dc10eaaa9281fa98ff625170 (patch) | |
| tree | acefe65067047f5c3c1c21f3ca08bfd0fb231685 | |
| parent | Add item about docs. (diff) | |
| download | gpgme-5f5faeafa1e17663dc10eaaa9281fa98ff625170.tar.gz gpgme-5f5faeafa1e17663dc10eaaa9281fa98ff625170.zip | |
doc/
2005-10-02  Marcus Brinkmann  <[email protected]>
	* gpgme.texi (Key Management): Add the new member notations of
	gpgme_sig_key_t.
	(Key Listing Mode): Document GPGME_KEYLIST_MODE_SIG_NOTATIONS.
gpgme/
2005-10-02  Marcus Brinkmann  <[email protected]>
	* util.h (_gpgme_decode_percent_string): Add new argument BINARY
	to prototype.
	* verify.c (parse_notation): Likewise for invocation.
	* conversion.c (_gpgme_decode_percent_string): Likewise to
	declaration.  If set, do not replace '\0' characters with a
	printable string.
	* gpgme.h (struct _gpgme_key_sig): New field notations.
	* ops.h (_gpgme_parse_notation): New prototype.
	* sig-notation.c (_gpgme_parse_notation): New function.
	* key.c (gpgme_key_unref): Free all signature notations.
	* keylist.c (op_data_t): New member tmp_keysig.
	(finish_key): Clear OPD->tmp_keysig.
	* gpgme.c (gpgme_set_keylist_mode): Remove check.
	* rungpg.c (gpg_keylist): Support listing signature notations.
	(gpg_keylist_ext): Likewise.
Diffstat (limited to '')
| -rw-r--r-- | NEWS | 8 | ||||
| -rw-r--r-- | doc/ChangeLog | 6 | ||||
| -rw-r--r-- | doc/gpgme.texi | 17 | ||||
| -rw-r--r-- | gpgme/ChangeLog | 18 | ||||
| -rw-r--r-- | gpgme/conversion.c | 8 | ||||
| -rw-r--r-- | gpgme/gpgme.c | 5 | ||||
| -rw-r--r-- | gpgme/gpgme.h | 15 | ||||
| -rw-r--r-- | gpgme/key.c | 14 | ||||
| -rw-r--r-- | gpgme/keylist.c | 64 | ||||
| -rw-r--r-- | gpgme/ops.h | 6 | ||||
| -rw-r--r-- | gpgme/rungpg.c | 14 | ||||
| -rw-r--r-- | gpgme/sig-notation.c | 141 | ||||
| -rw-r--r-- | gpgme/util.h | 5 | ||||
| -rw-r--r-- | gpgme/verify.c | 6 | 
14 files changed, 300 insertions, 27 deletions
| @@ -1,11 +1,15 @@  Noteworthy changes in version 1.x.y (unreleased)  ------------------------------------------------ - * bla bla bla ... + * Reading signature notations and policy URLs on key signatures is +   supported.  They can be found in the new field notations of the +   gpgme_key_sig_t structure.  This has to be enabled with the keylist +   mode flag GPGME_KEYLIST_MODE_SIG_NOTATIONS.   * Interface changes relative to the 1.0.3 release:  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -something			changed +gpgme_key_sig_t			EXTENDED: New field notations. +GPGME_KEYLIST_MODE_SIG_NOTATIONS NEW  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/ChangeLog b/doc/ChangeLog index 06a49c37..1afd92d9 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,9 @@ +2005-10-02  Marcus Brinkmann  <[email protected]> + +	* gpgme.texi (Key Management): Add the new member notations of +	gpgme_sig_key_t. +	(Key Listing Mode): Document GPGME_KEYLIST_MODE_SIG_NOTATIONS. +  2005-10-01  Marcus Brinkmann  <[email protected]>  	* gpgme.texi: Enclose all return parameters of deftypefuns in diff --git a/doc/gpgme.texi b/doc/gpgme.texi index 18b0bf46..a44cc60f 100644 --- a/doc/gpgme.texi +++ b/doc/gpgme.texi @@ -2136,6 +2136,12 @@ certificate server.  The @code{GPGME_KEYLIST_MODE_SIGS} symbol specifies that the key  signatures should be included in the listed keys. +@item GPGME_KEYLIST_MODE_SIG_NOTATIONS +The @code{GPGME_KEYLIST_MODE_SIG_NOTATIONS} symbol specifies that the +signature notations on key signatures should be included in the listed +keys.  This only works if @code{GPGME_KEYLIST_MODE_SIGS} is also +enabled. +  @item GPGME_KEYLIST_MODE_VALIDATE  The @code{GPGME_KEYLIST_MODE_VALIDATE} symbol specifies that the  backend should do key or certificate validation and not just get the @@ -2403,7 +2409,13 @@ validate user IDs on the key.  The signatures on a key are only available if the key was retrieved  via a listing operation with the @code{GPGME_KEYLIST_MODE_SIGS} mode -enabled, because it is expensive to retrieve all signatures of a key. +enabled, because it can be expensive to retrieve all signatures of a +key. + +The signature notations on a key signature are only available if the +key was retrieved via a listing operation with the +@code{GPGME_KEYLIST_MODE_SIG_NOTATIONS} mode enabled, because it can +be expensive to retrieve all signature notations.  The key signature structure has the following members: @@ -2458,6 +2470,9 @@ This is the comment component of @code{uid}, if available.  @item char *email  This is the email component of @code{uid}, if available. + +@item gpgme_sig_notation_t notations +This is a linked list with the notation data and policy URLs.  @end table  @end deftp diff --git a/gpgme/ChangeLog b/gpgme/ChangeLog index 21005564..f0e9c417 100644 --- a/gpgme/ChangeLog +++ b/gpgme/ChangeLog @@ -1,3 +1,21 @@ +2005-10-02  Marcus Brinkmann  <[email protected]> + +	* util.h (_gpgme_decode_percent_string): Add new argument BINARY +	to prototype. +	* verify.c (parse_notation): Likewise for invocation. +	* conversion.c (_gpgme_decode_percent_string): Likewise to +	declaration.  If set, do not replace '\0' characters with a +	printable string. +	* gpgme.h (struct _gpgme_key_sig): New field notations. +	* ops.h (_gpgme_parse_notation): New prototype. +	* sig-notation.c (_gpgme_parse_notation): New function. +	* key.c (gpgme_key_unref): Free all signature notations. +	* keylist.c (op_data_t): New member tmp_keysig. +	(finish_key): Clear OPD->tmp_keysig. +	* gpgme.c (gpgme_set_keylist_mode): Remove check. +	* rungpg.c (gpg_keylist): Support listing signature notations. +	(gpg_keylist_ext): Likewise. +  2005-10-01  Marcus Brinkmann  <[email protected]>  	* engine.h (_gpgme_set_engine_info): Add prototype. diff --git a/gpgme/conversion.c b/gpgme/conversion.c index 16854e5f..bba62bde 100644 --- a/gpgme/conversion.c +++ b/gpgme/conversion.c @@ -174,9 +174,11 @@ _gpgme_decode_c_string (const char *src, char **destp, size_t len)     large enough buffer is allocated with malloc and *DESTP is set to     the result.  Currently, LEN is only used to specify if allocation     is desired or not, the caller is expected to make sure that *DESTP -   is large enough if LEN is not zero.  */ +   is large enough if LEN is not zero.  If BINARY is 1, then '\0' +   characters are allowed in the output.  */  gpgme_error_t -_gpgme_decode_percent_string (const char *src, char **destp, size_t len) +_gpgme_decode_percent_string (const char *src, char **destp, size_t len, +			      int binary)  {    char *dest; @@ -222,7 +224,7 @@ _gpgme_decode_percent_string (const char *src, char **destp, size_t len)  	    }  	  else  	    { -	      if (!val) +	      if (!val && !binary)  		{  		  /* A binary zero is not representable in a C  		     string.  */ diff --git a/gpgme/gpgme.c b/gpgme/gpgme.c index da598339..e4a147ae 100644 --- a/gpgme/gpgme.c +++ b/gpgme/gpgme.c @@ -251,11 +251,6 @@ gpgme_get_include_certs (gpgme_ctx_t ctx)  gpgme_error_t  gpgme_set_keylist_mode (gpgme_ctx_t ctx, gpgme_keylist_mode_t mode)  { -  if (!((mode & GPGME_KEYLIST_MODE_LOCAL) -	|| (mode & GPGME_KEYLIST_MODE_EXTERN) -	|| (mode & GPGME_KEYLIST_MODE_SIGS))) -    return gpg_error (GPG_ERR_INV_VALUE); -    ctx->keylist_mode = mode;    return 0;  } diff --git a/gpgme/gpgme.h b/gpgme/gpgme.h index 48d722d7..c0e4e23c 100644 --- a/gpgme/gpgme.h +++ b/gpgme/gpgme.h @@ -305,10 +305,11 @@ gpgme_protocol_t;  /* The available keylist mode flags.  */ -#define GPGME_KEYLIST_MODE_LOCAL	1 -#define GPGME_KEYLIST_MODE_EXTERN	2 -#define GPGME_KEYLIST_MODE_SIGS		4 -#define GPGME_KEYLIST_MODE_VALIDATE	256 +#define GPGME_KEYLIST_MODE_LOCAL		1 +#define GPGME_KEYLIST_MODE_EXTERN		2 +#define GPGME_KEYLIST_MODE_SIGS			4 +#define GPGME_KEYLIST_MODE_SIG_NOTATIONS	8 +#define GPGME_KEYLIST_MODE_VALIDATE		256  typedef unsigned int gpgme_keylist_mode_t; @@ -594,6 +595,12 @@ struct _gpgme_key_sig    /* Crypto backend specific signature class.  */    unsigned int sig_class; + +  /* Notation data and policy URLs.  */ +  gpgme_sig_notation_t notations; + +  /* Internal to GPGME, do not use.  */ +  gpgme_sig_notation_t _last_notation;  };  typedef struct _gpgme_key_sig *gpgme_key_sig_t; diff --git a/gpgme/key.c b/gpgme/key.c index 23bd61eb..3dde670f 100644 --- a/gpgme/key.c +++ b/gpgme/key.c @@ -337,9 +337,19 @@ gpgme_key_unref (gpgme_key_t key)        while (keysig)  	{ -	  gpgme_key_sig_t next = keysig->next; +	  gpgme_key_sig_t next_keysig = keysig->next; +	  gpgme_sig_notation_t notation = keysig->notations; + +	  while (notation) +	    { +	      gpgme_sig_notation_t next_notation = notation->next; + +	      _gpgme_sig_notation_free (notation); +	      notation = next_notation; +	    } +            free (keysig); -	  keysig = next; +	  keysig = next_keysig;          }        free (uid);        uid = next_uid; diff --git a/gpgme/keylist.c b/gpgme/keylist.c index e786fe17..e05a4e36 100644 --- a/gpgme/keylist.c +++ b/gpgme/keylist.c @@ -48,8 +48,13 @@ typedef struct    struct _gpgme_op_keylist_result result;    gpgme_key_t tmp_key; +    /* This points to the last uid in tmp_key.  */    gpgme_user_id_t tmp_uid; + +  /* This points to the last sig in tmp_uid.  */ +  gpgme_key_sig_t tmp_keysig; +    /* Something new is available.  */    int key_cond;    struct key_queue_item_s *key_queue; @@ -64,8 +69,9 @@ release_op_data (void *hook)    if (opd->tmp_key)      gpgme_key_unref (opd->tmp_key); -  /* opd->tmp_uid is actually part of opd->tmp_key, so we do not need -     to release it here.  */ + +  /* opd->tmp_uid and opd->tmp_keysig are actually part of opd->tmp_key, +     so we do not need to release them here.  */    while (key)      { @@ -351,6 +357,7 @@ finish_key (gpgme_ctx_t ctx, op_data_t opd)    opd->tmp_key = NULL;    opd->tmp_uid = NULL; +  opd->tmp_keysig = NULL;    if (key)      _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_NEXT_KEY, key); @@ -365,7 +372,7 @@ keylist_colon_handler (void *priv, char *line)    enum      {        RT_NONE, RT_SIG, RT_UID, RT_SUB, RT_PUB, RT_FPR, -      RT_SSB, RT_SEC, RT_CRT, RT_CRS, RT_REV +      RT_SSB, RT_SEC, RT_CRT, RT_CRS, RT_REV, RT_SPK      }    rectype = RT_NONE;  #define NR_FIELDS 13 @@ -423,6 +430,8 @@ keylist_colon_handler (void *priv, char *line)      rectype = RT_SUB;     else if (!strcmp (field[0], "ssb") && key)      rectype = RT_SSB; +  else if (!strcmp (field[0], "spk") && key) +    rectype = RT_SPK;    else       rectype = RT_NONE; @@ -432,6 +441,12 @@ keylist_colon_handler (void *priv, char *line)    if (rectype != RT_SIG && rectype != RT_REV)      opd->tmp_uid = NULL; +  /* Only look at subpackets immediately following a signature.  For +     this, clear the signature pointer when encountering anything but +     a subpacket.  */ +  if (rectype != RT_SPK) +    opd->tmp_keysig = NULL; +    switch (rectype)      {      case RT_PUB: @@ -673,8 +688,51 @@ keylist_colon_handler (void *priv, char *line)  	    if (field[10][2] == 'x')  	      keysig->exportable = 1;  	  } + +      opd->tmp_keysig = keysig;        break; +    case RT_SPK: +      if (!opd->tmp_keysig) +	return 0; +      assert (opd->tmp_keysig == key->_last_uid->_last_keysig); + +      if (fields >= 4) +	{ +	  /* Field 2 has the subpacket type.  */ +	  int type = atoi (field[1]); + +	  /* Field 3 has the flags.  */ +	  int flags = atoi (field[2]); + +	  /* Field 4 has the length.  */ +	  int len = atoi (field[3]); + +	  /* Field 5 has the data.  */ +	  char *data = field[4]; + +	  /* Type 20: Notation data.  */ +	  /* Type 26: Policy URL.  */ +	  if (type == 20 || type == 26) +	    { +	      gpgme_sig_notation_t notation; + +	      keysig = opd->tmp_keysig; + +	      /* At this time, any error is serious.  */ +	      err = _gpgme_parse_notation (¬ation, type, flags, len, data); +	      if (err) +		return err; + +	      /* Add a new notation.  FIXME: Could be factored out.  */ +	      if (!keysig->notations) +		keysig->notations = notation; +	      if (keysig->_last_notation) +		keysig->_last_notation->next = notation; +	      keysig->_last_notation = notation; +	    } +	} +          case RT_NONE:        /* Unknown record.  */        break; diff --git a/gpgme/ops.h b/gpgme/ops.h index 9525e7c9..d6a09e52 100644 --- a/gpgme/ops.h +++ b/gpgme/ops.h @@ -160,4 +160,10 @@ gpgme_error_t _gpgme_sig_notation_create (gpgme_sig_notation_t *notationp,     pointer is ignored.  */  void _gpgme_sig_notation_free (gpgme_sig_notation_t notation); +/* Parse a notation or policy URL subpacket.  If the packet type is +   not known, return no error but NULL in NOTATION.  */ +gpgme_error_t _gpgme_parse_notation (gpgme_sig_notation_t *notationp, +				     int type, int pkflags, int len, +				     char *data); +  #endif /* OPS_H */ diff --git a/gpgme/rungpg.c b/gpgme/rungpg.c index e5ef0306..33c46eda 100644 --- a/gpgme/rungpg.c +++ b/gpgme/rungpg.c @@ -1617,6 +1617,13 @@ gpg_keylist (void *engine, const char *pattern, int secret_only,      err = add_arg (gpg, "--with-fingerprint");    if (!err)      err = add_arg (gpg, "--with-fingerprint"); +  if (!err && (mode & GPGME_KEYLIST_MODE_SIGS) +      && (mode & GPGME_KEYLIST_MODE_SIG_NOTATIONS)) +    { +      err = add_arg (gpg, "--list-options"); +      if (!err) +	err = add_arg (gpg, "show-sig-subpackets=\"20,26\""); +    }    if (!err)      err = add_arg (gpg, secret_only ? "--list-secret-keys"  		   : ((mode & GPGME_KEYLIST_MODE_SIGS) @@ -1652,6 +1659,13 @@ gpg_keylist_ext (void *engine, const char *pattern[], int secret_only,      err = add_arg (gpg, "--with-fingerprint");    if (!err)      err = add_arg (gpg, "--with-fingerprint"); +  if (!err && (mode & GPGME_KEYLIST_MODE_SIGS) +      && (mode & GPGME_KEYLIST_MODE_SIG_NOTATIONS)) +    { +      err = add_arg (gpg, "--list-options"); +      if (!err) +	err = add_arg (gpg, "show-sig-subpackets=\"20,26\""); +    }    if (!err)      err = add_arg (gpg, secret_only ? "--list-secret-keys"  		   : ((mode & GPGME_KEYLIST_MODE_SIGS) diff --git a/gpgme/sig-notation.c b/gpgme/sig-notation.c index 6a04fd03..5800c378 100644 --- a/gpgme/sig-notation.c +++ b/gpgme/sig-notation.c @@ -80,7 +80,10 @@ _gpgme_sig_notation_create (gpgme_sig_notation_t *notationp,    if (!notation)      return gpg_error_from_errno (errno); -  if (name_len) +  /* This is critical.  We want to reliably identify policy URLs by +     using a NULL pointer for NAME.  So all notations must have a NAME +     string, even if it is empty.  */ +  if (name)      {        /* We add a trailing '\0' for stringification in the good  	 case.  */ @@ -96,7 +99,7 @@ _gpgme_sig_notation_create (gpgme_sig_notation_t *notationp,        notation->name_len = name_len;      } -  if (value_len) +  if (value)      {        /* We add a trailing '\0' for stringification in the good  	 case.  */ @@ -121,3 +124,137 @@ _gpgme_sig_notation_create (gpgme_sig_notation_t *notationp,    _gpgme_sig_notation_free (notation);    return err;  } + + +/* GnuPG subpacket flags.  */ + +/* This subpacket data is part of the hashed data.  */ +#define GNUPG_SPK_HASHED	0x01 + +/* This subpacket is marked critical.  */ +#define GNUPG_SPK_CRITICAL	0x02 + +/* Parse a notation or policy URL subpacket.  If the packet type is +   not known, return no error but NULL in NOTATION.  */ +gpgme_error_t +_gpgme_parse_notation (gpgme_sig_notation_t *notationp, +		       int type, int pkflags, int len, char *data) +{ +  gpgme_error_t err; +  char *name = NULL; +  int name_len = 0; +  char *value = NULL; +  int value_len = 0; +  gpgme_sig_notation_flags_t flags = 0; +  char *decoded_data; +  unsigned char *bdata; + +  /* Type 20: Notation data.  */ +  /* Type 26: Policy URL.  */ +  if (type != 20 && type != 26) +    { +      *notationp = NULL; +      return 0; +    } + +  /* A few simple sanity checks.  */ +  if (len > strlen (data)) +    return gpg_error (GPG_ERR_INV_ENGINE); + +  /* See below for the format of a notation subpacket.  It has at +     least four octets of flags and two times two octets of length +     information.  */ +  if (type == 20 && len < 4 + 2 + 2) +    return gpg_error (GPG_ERR_INV_ENGINE); + +  err = _gpgme_decode_percent_string (data, &decoded_data, 0, 1); +  if (err) +    return err; +  bdata = (unsigned char *) decoded_data; + +  /* Flags common to notation data and policy URL.  */ +  if (pkflags & GNUPG_SPK_CRITICAL) +    flags |= GPGME_SIG_NOTATION_CRITICAL; + +  /* This information is relevant in parsing multi-octet numbers below: + +     3.1. Scalar numbers + +     Scalar numbers are unsigned, and are always stored in big-endian +     format.  Using n[k] to refer to the kth octet being interpreted, +     the value of a two-octet scalar is ((n[0] << 8) + n[1]).  The +     value of a four-octet scalar is ((n[0] << 24) + (n[1] << 16) + +     (n[2] << 8) + n[3]). + +     From RFC2440: OpenPGP Message Format.  Copyright (C) The Internet +     Society (1998).  All Rights Reserved.  */ +#define RFC2440_GET_WORD(chr) ((((int)((unsigned char *)(chr))[0]) << 8) \ +			       + ((int)((unsigned char *)(chr))[1])) + +  if (type == 20) +    { +      /* 5.2.3.15. Notation Data + +	 (4 octets of flags, 2 octets of name length (M), +	 2 octets of value length (N), M octets of name data, +	 N octets of value data) + +	 [...] The "flags" field holds four octets of flags. +	 All undefined flags MUST be zero. Defined flags are: + +	 First octet: 0x80 = human-readable. [...] +	 Other octets:  none. + +	 From RFC2440: OpenPGP Message Format.  Copyright (C) The +	 Internet Society (1998).  All Rights Reserved.  */ + +      int chr; + +      /* First octet of flags.  */ +#define RFC2440_SPK20_FLAG1_HUMAN_READABLE 0x80 + +      chr = *bdata; +      bdata++; + +      if (chr & RFC2440_SPK20_FLAG1_HUMAN_READABLE) +	flags |= GPGME_SIG_NOTATION_HUMAN_READABLE; + +      /* The second, third and four octet of flags are unused.  */ +      bdata++; +      bdata++; +      bdata++; + +      name_len = RFC2440_GET_WORD (bdata); +      bdata += 2; + +      value_len = RFC2440_GET_WORD (bdata); +      bdata += 2; + +      /* Small sanity check.  */ +      if (4 + 2 + 2 + name_len + value_len > len) +	{ +	  free (decoded_data); +	  return gpg_error (GPG_ERR_INV_ENGINE); +	} + +      name = (char *) bdata; +      bdata += name_len; + +      value = (char *) bdata; +    } +  else +    { +      /* Type is 26.  */ + +      /* NAME is NULL, name_len is 0.  */ + +      value = (char *) bdata; +      value_len = strlen (value); +    } + +  err = _gpgme_sig_notation_create (notationp, name, name_len, +				    value, value_len, flags); + +  free (decoded_data); +  return err; +} diff --git a/gpgme/util.h b/gpgme/util.h index 6d542373..3c724cad 100644 --- a/gpgme/util.h +++ b/gpgme/util.h @@ -80,9 +80,10 @@ gpgme_error_t _gpgme_decode_c_string (const char *src, char **destp,     large enough buffer is allocated with malloc and *DESTP is set to     the result.  Currently, LEN is only used to specify if allocation     is desired or not, the caller is expected to make sure that *DESTP -   is large enough if LEN is not zero.  */ +   is large enough if LEN is not zero.  If BINARY is 1, then '\0' +   characters are allowed in the output.  */  gpgme_error_t _gpgme_decode_percent_string (const char *src, char **destp, -					    size_t len); +					    size_t len, int binary);  /* Parse the string TIMESTAMP into a time_t.  The string may either be diff --git a/gpgme/verify.c b/gpgme/verify.c index d7c32169..bfce4c89 100644 --- a/gpgme/verify.c +++ b/gpgme/verify.c @@ -434,7 +434,7 @@ parse_notation (gpgme_signature_t sig, gpgme_status_code_t code, char *args)        if (code == GPGME_STATUS_NOTATION_NAME)  	{ -	  err = _gpgme_decode_percent_string (args, ¬ation->name, 0); +	  err = _gpgme_decode_percent_string (args, ¬ation->name, 0, 0);  	  if (err)  	    {  	      _gpgme_sig_notation_free (notation); @@ -453,7 +453,7 @@ parse_notation (gpgme_signature_t sig, gpgme_status_code_t code, char *args)  	{  	  /* This is a policy URL.  */ -	  err = _gpgme_decode_percent_string (args, ¬ation->value, 0); +	  err = _gpgme_decode_percent_string (args, ¬ation->value, 0, 0);  	  if (err)  	    {  	      _gpgme_sig_notation_free (notation); @@ -497,7 +497,7 @@ parse_notation (gpgme_signature_t sig, gpgme_status_code_t code, char *args)  	  dest += cur_len;  	} -      err = _gpgme_decode_percent_string (args, &dest, len); +      err = _gpgme_decode_percent_string (args, &dest, len, 0);        if (err)  	return err; | 
