From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- security/nss/lib/util/pkcs11uri.c | 867 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 867 insertions(+) create mode 100644 security/nss/lib/util/pkcs11uri.c (limited to 'security/nss/lib/util/pkcs11uri.c') diff --git a/security/nss/lib/util/pkcs11uri.c b/security/nss/lib/util/pkcs11uri.c new file mode 100644 index 0000000000..e0c4077a3d --- /dev/null +++ b/security/nss/lib/util/pkcs11uri.c @@ -0,0 +1,867 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "pkcs11.h" +#include "pkcs11uri.h" +#include "plarena.h" +#include "prprf.h" +#include "secport.h" + +/* Character sets used in the ABNF rules in RFC7512. */ +#define PK11URI_DIGIT "0123456789" +#define PK11URI_ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define PK11URI_HEXDIG PK11URI_DIGIT "abcdefABCDEF" +#define PK11URI_UNRESERVED PK11URI_ALPHA PK11URI_DIGIT "-._~" +#define PK11URI_RES_AVAIL ":[]@!$'()*+,=" +#define PK11URI_PATH_RES_AVAIL PK11URI_RES_AVAIL "&" +#define PK11URI_QUERY_RES_AVAIL PK11URI_RES_AVAIL "/?|" +#define PK11URI_ATTR_NM_CHAR PK11URI_ALPHA PK11URI_DIGIT "-_" +#define PK11URI_PCHAR PK11URI_UNRESERVED PK11URI_PATH_RES_AVAIL +#define PK11URI_QCHAR PK11URI_UNRESERVED PK11URI_QUERY_RES_AVAIL + +/* Path attributes defined in RFC7512. */ +static const char *pattr_names[] = { + PK11URI_PATTR_TOKEN, + PK11URI_PATTR_MANUFACTURER, + PK11URI_PATTR_SERIAL, + PK11URI_PATTR_MODEL, + PK11URI_PATTR_LIBRARY_MANUFACTURER, + PK11URI_PATTR_LIBRARY_DESCRIPTION, + PK11URI_PATTR_LIBRARY_VERSION, + PK11URI_PATTR_OBJECT, + PK11URI_PATTR_TYPE, + PK11URI_PATTR_ID, + PK11URI_PATTR_SLOT_MANUFACTURER, + PK11URI_PATTR_SLOT_DESCRIPTION, + PK11URI_PATTR_SLOT_ID +}; + +/* Query attributes defined in RFC7512. */ +static const char *qattr_names[] = { + PK11URI_QATTR_PIN_SOURCE, + PK11URI_QATTR_PIN_VALUE, + PK11URI_QATTR_MODULE_NAME, + PK11URI_QATTR_MODULE_PATH +}; + +struct PK11URIBufferStr { + PLArenaPool *arena; + unsigned char *data; + size_t size; + size_t allocated; +}; +typedef struct PK11URIBufferStr PK11URIBuffer; + +struct PK11URIAttributeListEntryStr { + char *name; + SECItem value; +}; +typedef struct PK11URIAttributeListEntryStr PK11URIAttributeListEntry; + +struct PK11URIAttributeListStr { + PLArenaPool *arena; + PK11URIAttributeListEntry *attrs; + size_t num_attrs; +}; +typedef struct PK11URIAttributeListStr PK11URIAttributeList; + +struct PK11URIStr { + PLArenaPool *arena; + + PK11URIAttributeList pattrs; + PK11URIAttributeList vpattrs; + + PK11URIAttributeList qattrs; + PK11URIAttributeList vqattrs; +}; + +#define PK11URI_ARENA_SIZE 1024 + +typedef int (*PK11URIAttributeCompareNameFunc)(const char *a, const char *b); + +/* This belongs in secport.h */ +#define PORT_ArenaGrowArray(poolp, oldptr, type, oldnum, newnum) \ + (type *)PORT_ArenaGrow((poolp), (oldptr), \ + (oldnum) * sizeof(type), (newnum) * sizeof(type)) +#define PORT_ReallocArray(oldptr, type, newnum) \ + (type *)PORT_Realloc((oldptr), (newnum) * sizeof(type)) + +/* Functions for resizable buffer. */ +static SECStatus +pk11uri_AppendBuffer(PK11URIBuffer *buffer, const unsigned char *data, + size_t size) +{ + /* Check overflow. */ + if (buffer->size + size < buffer->size) + return SECFailure; + + if (buffer->size + size > buffer->allocated) { + size_t allocated = buffer->allocated * 2 + size; + if (allocated < buffer->allocated) + return SECFailure; + if (buffer->arena) + buffer->data = PORT_ArenaGrow(buffer->arena, buffer->data, + buffer->allocated, allocated); + else + buffer->data = PORT_Realloc(buffer->data, allocated); + if (buffer->data == NULL) + return SECFailure; + buffer->allocated = allocated; + } + + memcpy(&buffer->data[buffer->size], data, size); + buffer->size += size; + + return SECSuccess; +} + +static void +pk11uri_InitBuffer(PK11URIBuffer *buffer, PLArenaPool *arena) +{ + memset(buffer, 0, sizeof(PK11URIBuffer)); + buffer->arena = arena; +} + +static void +pk11uri_DestroyBuffer(PK11URIBuffer *buffer) +{ + if (buffer->arena == NULL) { + PORT_Free(buffer->data); + } +} + +/* URI encoding functions. */ +static char * +pk11uri_Escape(PLArenaPool *arena, const unsigned char *value, size_t length, + const char *available) +{ + PK11URIBuffer buffer; + const unsigned char *p; + unsigned char buf[4]; + char *result = NULL; + SECStatus ret; + + pk11uri_InitBuffer(&buffer, arena); + + for (p = value; p < value + length; p++) { + if (strchr(available, *p) == NULL) { + if (PR_snprintf((char *)buf, sizeof(buf), "%%%02X", *p) == (PRUint32)-1) { + goto fail; + } + ret = pk11uri_AppendBuffer(&buffer, buf, 3); + if (ret != SECSuccess) { + goto fail; + } + } else { + ret = pk11uri_AppendBuffer(&buffer, p, 1); + if (ret != SECSuccess) { + goto fail; + } + } + } + buf[0] = '\0'; + ret = pk11uri_AppendBuffer(&buffer, buf, 1); + if (ret != SECSuccess) { + goto fail; + } + + /* Steal the memory allocated in buffer. */ + result = (char *)buffer.data; + buffer.data = NULL; + +fail: + pk11uri_DestroyBuffer(&buffer); + + return result; +} + +static unsigned char * +pk11uri_Unescape(PLArenaPool *arena, const char *value, size_t *length) +{ + PK11URIBuffer buffer; + const char *p; + unsigned char buf[1]; + unsigned char *result = NULL; + SECStatus ret; + + pk11uri_InitBuffer(&buffer, arena); + + for (p = value; p < value + *length; p++) { + if (*p == '%') { + int c; + size_t i; + + p++; + for (c = 0, i = 0; i < 2; i++) { + int h = *(p + i); + if ('0' <= h && h <= '9') { + c = (c << 4) | (h - '0'); + } else if ('a' <= h && h <= 'f') { + c = (c << 4) | (h - 'a' + 10); + } else if ('A' <= h && h <= 'F') { + c = (c << 4) | (h - 'A' + 10); + } else { + break; + } + } + if (i != 2) { + goto fail; + } + p++; + buf[0] = c; + } else { + buf[0] = *p; + } + ret = pk11uri_AppendBuffer(&buffer, buf, 1); + if (ret != SECSuccess) { + goto fail; + } + } + *length = buffer.size; + buf[0] = '\0'; + ret = pk11uri_AppendBuffer(&buffer, buf, 1); + if (ret != SECSuccess) { + goto fail; + } + + result = buffer.data; + buffer.data = NULL; + +fail: + pk11uri_DestroyBuffer(&buffer); + + return result; +} + +/* Functions for manipulating attributes array. */ + +/* Compare two attribute names by the array index in attr_names. Both + * attribute names must be present in attr_names, otherwise it is a + * programming error. */ +static int +pk11uri_CompareByPosition(const char *a, const char *b, + const char **attr_names, size_t num_attr_names) +{ + size_t i, j; + + for (i = 0; i < num_attr_names; i++) { + if (strcmp(a, attr_names[i]) == 0) { + break; + } + } + PR_ASSERT(i < num_attr_names); + + for (j = 0; j < num_attr_names; j++) { + if (strcmp(b, attr_names[j]) == 0) { + break; + } + } + PR_ASSERT(j < num_attr_names); + + return i - j; +} + +/* Those pk11uri_Compare{Path,Query}AttributeName functions are used + * to reorder attributes when inserting. */ +static int +pk11uri_ComparePathAttributeName(const char *a, const char *b) +{ + return pk11uri_CompareByPosition(a, b, pattr_names, PR_ARRAY_SIZE(pattr_names)); +} + +static int +pk11uri_CompareQueryAttributeName(const char *a, const char *b) +{ + return pk11uri_CompareByPosition(a, b, qattr_names, PR_ARRAY_SIZE(qattr_names)); +} + +static SECStatus +pk11uri_InsertToAttributeList(PK11URIAttributeList *attrs, + char *name, unsigned char *value, size_t size, + PK11URIAttributeCompareNameFunc compare_name, + PRBool allow_duplicate) +{ + size_t i; + + if (attrs->arena) { + attrs->attrs = PORT_ArenaGrowArray(attrs->arena, attrs->attrs, + PK11URIAttributeListEntry, + attrs->num_attrs, + attrs->num_attrs + 1); + } else { + attrs->attrs = PORT_ReallocArray(attrs->attrs, + PK11URIAttributeListEntry, + attrs->num_attrs + 1); + } + if (attrs->attrs == NULL) { + return SECFailure; + } + + for (i = 0; i < attrs->num_attrs; i++) { + if (!allow_duplicate && strcmp(name, attrs->attrs[i].name) == 0) { + return SECFailure; + } + if (compare_name(name, attrs->attrs[i].name) < 0) { + memmove(&attrs->attrs[i + 1], &attrs->attrs[i], + sizeof(PK11URIAttributeListEntry) * (attrs->num_attrs - i)); + break; + } + } + + attrs->attrs[i].name = name; + attrs->attrs[i].value.type = siBuffer; + attrs->attrs[i].value.data = value; + attrs->attrs[i].value.len = size; + + attrs->num_attrs++; + + return SECSuccess; +} + +static SECStatus +pk11uri_InsertToAttributeListEscaped(PK11URIAttributeList *attrs, + const char *name, size_t name_size, + const char *value, size_t value_size, + PK11URIAttributeCompareNameFunc compare_name, + PRBool allow_duplicate) +{ + char *name_copy = NULL; + unsigned char *value_copy = NULL; + SECStatus ret; + + if (attrs->arena) { + name_copy = PORT_ArenaNewArray(attrs->arena, char, name_size + 1); + } else { + name_copy = PORT_Alloc(name_size + 1); + } + if (name_copy == NULL) { + goto fail; + } + memcpy(name_copy, name, name_size); + name_copy[name_size] = '\0'; + + value_copy = pk11uri_Unescape(attrs->arena, value, &value_size); + if (value_copy == NULL) { + goto fail; + } + + ret = pk11uri_InsertToAttributeList(attrs, name_copy, value_copy, value_size, + compare_name, allow_duplicate); + if (ret != SECSuccess) { + goto fail; + } + + return ret; + +fail: + if (attrs->arena == NULL) { + PORT_Free(name_copy); + PORT_Free(value_copy); + } + + return SECFailure; +} + +static void +pk11uri_InitAttributeList(PK11URIAttributeList *attrs, PLArenaPool *arena) +{ + memset(attrs, 0, sizeof(PK11URIAttributeList)); + attrs->arena = arena; +} + +static void +pk11uri_DestroyAttributeList(PK11URIAttributeList *attrs) +{ + if (attrs->arena == NULL) { + size_t i; + + for (i = 0; i < attrs->num_attrs; i++) { + PORT_Free(attrs->attrs[i].name); + PORT_Free(attrs->attrs[i].value.data); + } + PORT_Free(attrs->attrs); + } +} + +static SECStatus +pk11uri_AppendAttributeListToBuffer(PK11URIBuffer *buffer, + PK11URIAttributeList *attrs, + int separator, + const char *unescaped) +{ + size_t i; + SECStatus ret; + + for (i = 0; i < attrs->num_attrs; i++) { + unsigned char sep[1]; + char *escaped; + PK11URIAttributeListEntry *attr = &attrs->attrs[i]; + + if (i > 0) { + sep[0] = separator; + ret = pk11uri_AppendBuffer(buffer, sep, 1); + if (ret != SECSuccess) { + return ret; + } + } + + ret = pk11uri_AppendBuffer(buffer, (unsigned char *)attr->name, + strlen(attr->name)); + if (ret != SECSuccess) { + return ret; + } + + sep[0] = '='; + ret = pk11uri_AppendBuffer(buffer, sep, 1); + if (ret != SECSuccess) { + return ret; + } + + escaped = pk11uri_Escape(buffer->arena, attr->value.data, attr->value.len, + unescaped); + if (escaped == NULL) { + return ret; + } + ret = pk11uri_AppendBuffer(buffer, (unsigned char *)escaped, + strlen(escaped)); + if (buffer->arena == NULL) { + PORT_Free(escaped); + } + if (ret != SECSuccess) { + return ret; + } + } + + return SECSuccess; +} + +/* Creation of PK11URI object. */ +static PK11URI * +pk11uri_AllocURI(void) +{ + PLArenaPool *arena; + PK11URI *result; + + arena = PORT_NewArena(PK11URI_ARENA_SIZE); + if (arena == NULL) { + return NULL; + } + + result = PORT_ArenaZAlloc(arena, sizeof(PK11URI)); + if (result == NULL) { + PORT_FreeArena(arena, PR_FALSE); + return NULL; + } + + result->arena = arena; + pk11uri_InitAttributeList(&result->pattrs, arena); + pk11uri_InitAttributeList(&result->vpattrs, arena); + pk11uri_InitAttributeList(&result->qattrs, arena); + pk11uri_InitAttributeList(&result->vqattrs, arena); + + return result; +} + +static SECStatus +pk11uri_InsertAttributes(PK11URIAttributeList *dest_attrs, + PK11URIAttributeList *dest_vattrs, + const PK11URIAttribute *attrs, + size_t num_attrs, + const char **attr_names, + size_t num_attr_names, + PK11URIAttributeCompareNameFunc compare_name, + PRBool allow_duplicate, + PRBool vendor_allow_duplicate) +{ + SECStatus ret; + size_t i; + + for (i = 0; i < num_attrs; i++) { + char *name, *value; + const char *p; + size_t j; + + p = attrs[i].name; + + /* The attribute must not be empty. */ + if (*p == '\0') { + return SECFailure; + } + + /* Check that the name doesn't contain invalid character. */ + for (; *p != '\0'; p++) { + if (strchr(PK11URI_ATTR_NM_CHAR, *p) == NULL) { + return SECFailure; + } + } + + name = PORT_ArenaStrdup(dest_attrs->arena, attrs[i].name); + if (name == NULL) { + return SECFailure; + } + + value = PORT_ArenaStrdup(dest_attrs->arena, attrs[i].value); + if (value == NULL) { + return SECFailure; + } + + for (j = 0; j < num_attr_names; j++) { + if (strcmp(name, attr_names[j]) == 0) { + break; + } + } + if (j < num_attr_names) { + /* Named attribute. */ + ret = pk11uri_InsertToAttributeList(dest_attrs, + name, + (unsigned char *)value, + strlen(value), + compare_name, + allow_duplicate); + if (ret != SECSuccess) { + return ret; + } + } else { + /* Vendor attribute. */ + ret = pk11uri_InsertToAttributeList(dest_vattrs, + name, + (unsigned char *)value, + strlen(value), + strcmp, + vendor_allow_duplicate); + if (ret != SECSuccess) { + return ret; + } + } + } + + return SECSuccess; +} + +PK11URI * +PK11URI_CreateURI(const PK11URIAttribute *pattrs, + size_t num_pattrs, + const PK11URIAttribute *qattrs, + size_t num_qattrs) +{ + PK11URI *result; + SECStatus ret; + + result = pk11uri_AllocURI(); + + ret = pk11uri_InsertAttributes(&result->pattrs, &result->vpattrs, + pattrs, num_pattrs, + pattr_names, PR_ARRAY_SIZE(pattr_names), + pk11uri_ComparePathAttributeName, + PR_FALSE, PR_FALSE); + if (ret != SECSuccess) { + goto fail; + } + + ret = pk11uri_InsertAttributes(&result->qattrs, &result->vqattrs, + qattrs, num_qattrs, + qattr_names, PR_ARRAY_SIZE(qattr_names), + pk11uri_CompareQueryAttributeName, + PR_FALSE, PR_TRUE); + if (ret != SECSuccess) { + goto fail; + } + + return result; + +fail: + PK11URI_DestroyURI(result); + + return NULL; +} + +/* Parsing. */ +static SECStatus +pk11uri_ParseAttributes(const char **string, + const char *stop_chars, + int separator, + const char *accept_chars, + const char **attr_names, size_t num_attr_names, + PK11URIAttributeList *attrs, + PK11URIAttributeList *vattrs, + PK11URIAttributeCompareNameFunc compare_name, + PRBool allow_duplicate, + PRBool vendor_allow_duplicate) +{ + const char *p = *string; + + for (; *p != '\0'; p++) { + const char *name_start, *name_end, *value_start, *value_end; + size_t name_length, value_length, i; + SECStatus ret; + + if (strchr(stop_chars, *p) != NULL) { + break; + } + for (name_start = p; *p != '=' && *p != '\0'; p++) { + if (strchr(PK11URI_ATTR_NM_CHAR, *p) != NULL) + continue; + + return SECFailure; + } + if (*p == '\0') { + return SECFailure; + } + name_end = p++; + + /* The attribute name must not be empty. */ + if (name_end == name_start) { + return SECFailure; + } + + for (value_start = p; *p != separator && *p != '\0'; p++) { + if (strchr(stop_chars, *p) != NULL) { + break; + } + if (strchr(accept_chars, *p) != NULL) { + continue; + } + if (*p == '%') { + const char ch2 = *++p; + if (strchr(PK11URI_HEXDIG, ch2) != NULL) { + const char ch3 = *++p; + if (strchr(PK11URI_HEXDIG, ch3) != NULL) + continue; + } + } + + return SECFailure; + } + value_end = p; + + name_length = name_end - name_start; + value_length = value_end - value_start; + + for (i = 0; i < num_attr_names; i++) { + if (name_length == strlen(attr_names[i]) && + memcmp(name_start, attr_names[i], name_length) == 0) { + break; + } + } + if (i < num_attr_names) { + /* Named attribute. */ + ret = pk11uri_InsertToAttributeListEscaped(attrs, + name_start, name_length, + value_start, value_length, + compare_name, + allow_duplicate); + if (ret != SECSuccess) { + return ret; + } + } else { + /* Vendor attribute. */ + ret = pk11uri_InsertToAttributeListEscaped(vattrs, + name_start, name_length, + value_start, value_length, + strcmp, + vendor_allow_duplicate); + if (ret != SECSuccess) { + return ret; + } + } + + if (*p == '?' || *p == '\0') { + break; + } + } + + *string = p; + return SECSuccess; +} + +PK11URI * +PK11URI_ParseURI(const char *string) +{ + PK11URI *result; + const char *p = string; + SECStatus ret; + + if (PORT_Strncasecmp("pkcs11:", p, 7) != 0) { + return NULL; + } + p += 7; + + result = pk11uri_AllocURI(); + if (result == NULL) { + return NULL; + } + + /* Parse the path component and its attributes. */ + ret = pk11uri_ParseAttributes(&p, "?", ';', PK11URI_PCHAR, + pattr_names, PR_ARRAY_SIZE(pattr_names), + &result->pattrs, &result->vpattrs, + pk11uri_ComparePathAttributeName, + PR_FALSE, PR_FALSE); + if (ret != SECSuccess) { + goto fail; + } + + /* Parse the query component and its attributes. */ + if (*p == '?') { + p++; + ret = pk11uri_ParseAttributes(&p, "", '&', PK11URI_QCHAR, + qattr_names, PR_ARRAY_SIZE(qattr_names), + &result->qattrs, &result->vqattrs, + pk11uri_CompareQueryAttributeName, + PR_FALSE, PR_TRUE); + if (ret != SECSuccess) { + goto fail; + } + } + + return result; + +fail: + PK11URI_DestroyURI(result); + + return NULL; +} + +/* Formatting. */ +char * +PK11URI_FormatURI(PLArenaPool *arena, PK11URI *uri) +{ + PK11URIBuffer buffer; + SECStatus ret; + char *result = NULL; + + pk11uri_InitBuffer(&buffer, arena); + + ret = pk11uri_AppendBuffer(&buffer, (unsigned char *)"pkcs11:", 7); + if (ret != SECSuccess) + goto fail; + + ret = pk11uri_AppendAttributeListToBuffer(&buffer, &uri->pattrs, ';', PK11URI_PCHAR); + if (ret != SECSuccess) { + goto fail; + } + + if (uri->pattrs.num_attrs > 0 && uri->vpattrs.num_attrs > 0) { + ret = pk11uri_AppendBuffer(&buffer, (unsigned char *)";", 1); + if (ret != SECSuccess) { + goto fail; + } + } + + ret = pk11uri_AppendAttributeListToBuffer(&buffer, &uri->vpattrs, ';', + PK11URI_PCHAR); + if (ret != SECSuccess) { + goto fail; + } + + if (uri->qattrs.num_attrs > 0 || uri->vqattrs.num_attrs > 0) { + ret = pk11uri_AppendBuffer(&buffer, (unsigned char *)"?", 1); + if (ret != SECSuccess) { + goto fail; + } + } + + ret = pk11uri_AppendAttributeListToBuffer(&buffer, &uri->qattrs, '&', PK11URI_QCHAR); + if (ret != SECSuccess) { + goto fail; + } + + if (uri->qattrs.num_attrs > 0 && uri->vqattrs.num_attrs > 0) { + ret = pk11uri_AppendBuffer(&buffer, (unsigned char *)"&", 1); + if (ret != SECSuccess) { + goto fail; + } + } + + ret = pk11uri_AppendAttributeListToBuffer(&buffer, &uri->vqattrs, '&', + PK11URI_QCHAR); + if (ret != SECSuccess) { + goto fail; + } + + ret = pk11uri_AppendBuffer(&buffer, (unsigned char *)"\0", 1); + if (ret != SECSuccess) { + goto fail; + } + + result = (char *)buffer.data; + buffer.data = NULL; + +fail: + pk11uri_DestroyBuffer(&buffer); + + return result; +} + +/* Deallocating. */ +void +PK11URI_DestroyURI(PK11URI *uri) +{ + pk11uri_DestroyAttributeList(&uri->pattrs); + pk11uri_DestroyAttributeList(&uri->vpattrs); + pk11uri_DestroyAttributeList(&uri->qattrs); + pk11uri_DestroyAttributeList(&uri->vqattrs); + PORT_FreeArena(uri->arena, PR_FALSE); +} + +/* Accessors. */ +static const SECItem * +pk11uri_GetAttribute(PK11URIAttributeList *attrs, + PK11URIAttributeList *vattrs, + const char *name) +{ + size_t i; + + for (i = 0; i < attrs->num_attrs; i++) { + if (strcmp(name, attrs->attrs[i].name) == 0) { + return &attrs->attrs[i].value; + } + } + + for (i = 0; i < vattrs->num_attrs; i++) { + if (strcmp(name, vattrs->attrs[i].name) == 0) { + return &vattrs->attrs[i].value; + } + } + + return NULL; +} + +const SECItem * +PK11URI_GetPathAttributeItem(PK11URI *uri, const char *name) +{ + return pk11uri_GetAttribute(&uri->pattrs, &uri->vpattrs, name); +} + +const char * +PK11URI_GetPathAttribute(PK11URI *uri, const char *name) +{ + const SECItem *value; + + value = PK11URI_GetPathAttributeItem(uri, name); + if (!value) { + return NULL; + } + + return (const char *)value->data; +} + +const SECItem * +PK11URI_GetQueryAttributeItem(PK11URI *uri, const char *name) +{ + return pk11uri_GetAttribute(&uri->qattrs, &uri->vqattrs, name); +} + +const char * +PK11URI_GetQueryAttribute(PK11URI *uri, const char *name) +{ + const SECItem *value; + + value = PK11URI_GetQueryAttributeItem(uri, name); + if (!value) { + return NULL; + } + + return (const char *)value->data; +} -- cgit v1.2.3