Libkleo

dn.cpp
1/*
2 dn.cpp
3
4 This file is part of libkleopatra, the KDE keymanagement library
5 SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
6 SPDX-FileCopyrightText: 2021 g10 Code GmbH
7 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
8
9 DN parsing:
10 SPDX-FileCopyrightText: 2002 g10 Code GmbH
11 SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
12
13 SPDX-License-Identifier: GPL-2.0-or-later
14*/
15
16#include <config-libkleo.h>
17
18#include "dn.h"
19#include "libkleo_debug.h"
20
21#include "oidmap.h"
22
23#include <KLazyLocalizedString>
24
25#include <algorithm>
26
27#ifdef _MSC_VER
28#include <string.h>
29#define strcasecmp _stricmp
30#endif
31
32namespace
33{
34static const QStringList defaultOrder = {
35 QStringLiteral("CN"),
36 QStringLiteral("L"),
37 QStringLiteral("_X_"),
38 QStringLiteral("OU"),
39 QStringLiteral("O"),
40 QStringLiteral("C"),
41};
42
43class DNAttributeOrderStore
44{
45 DNAttributeOrderStore()
46 : mAttributeOrder{defaultOrder}
47 {
48 }
49
50public:
51 static DNAttributeOrderStore *instance()
52 {
53 static DNAttributeOrderStore *self = new DNAttributeOrderStore();
54 return self;
55 }
56
57 const QStringList &attributeOrder() const
58 {
59 return mAttributeOrder.empty() ? defaultOrder : mAttributeOrder;
60 }
61
62 void setAttributeOrder(const QStringList &order)
63 {
64 mAttributeOrder = order;
65 }
66
67private:
68 QStringList mAttributeOrder;
69};
70}
71
72class Kleo::DN::Private
73{
74public:
75 Private()
76 : mRefCount(0)
77 {
78 }
79 Private(const Private &other)
80 : attributes(other.attributes)
81 , reorderedAttributes(other.reorderedAttributes)
82 , mRefCount(0)
83 {
84 }
85
86 int ref()
87 {
88 return ++mRefCount;
89 }
90
91 int unref()
92 {
93 if (--mRefCount <= 0) {
94 delete this;
95 return 0;
96 } else {
97 return mRefCount;
98 }
99 }
100
101 int refCount() const
102 {
103 return mRefCount;
104 }
105
106 DN::Attribute::List attributes;
107 DN::Attribute::List reorderedAttributes;
108
109private:
110 int mRefCount;
111};
112
113namespace
114{
115struct DnPair {
116 char *key;
117 char *value;
118};
119}
120
121// copied from CryptPlug and adapted to work on DN::Attribute::List:
122
123#define digitp(p) (*(p) >= '0' && *(p) <= '9')
124#define hexdigitp(a) (digitp(a) || (*(a) >= 'A' && *(a) <= 'F') || (*(a) >= 'a' && *(a) <= 'f'))
125#define xtoi_1(p) (*(p) <= '9' ? (*(p) - '0') : *(p) <= 'F' ? (*(p) - 'A' + 10) : (*(p) - 'a' + 10))
126#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p) + 1))
127
128static char *trim_trailing_spaces(char *string)
129{
130 char *p;
131 char *mark;
132
133 for (mark = nullptr, p = string; *p; p++) {
134 if (isspace(*p)) {
135 if (!mark) {
136 mark = p;
137 }
138 } else {
139 mark = nullptr;
140 }
141 }
142 if (mark) {
143 *mark = '\0';
144 }
145
146 return string;
147}
148
149/* Parse a DN and return an array-ized one. This is not a validating
150 parser and it does not support any old-stylish syntax; gpgme is
151 expected to return only rfc2253 compatible strings. */
152static const unsigned char *parse_dn_part(DnPair *array, const unsigned char *string)
153{
154 const unsigned char *s;
155 const unsigned char *s1;
156 size_t n;
157 char *p;
158
159 /* parse attributeType */
160 for (s = string + 1; *s && *s != '='; s++) {
161 ;
162 }
163 if (!*s) {
164 return nullptr; /* error */
165 }
166 n = s - string;
167 if (!n) {
168 return nullptr; /* empty key */
169 }
170 p = (char *)malloc(n + 1);
171
172 memcpy(p, string, n);
173 p[n] = 0;
174 trim_trailing_spaces((char *)p);
175 // map OIDs to their names:
176 if (const char *name = Kleo::attributeNameForOID(p)) {
177 free(p);
178 p = strdup(name);
179 }
180 array->key = p;
181 string = s + 1;
182
183 if (*string == '#') {
184 /* hexstring */
185 string++;
186 for (s = string; hexdigitp(s); s++)
187 ;
188 n = s - string;
189 if (!n || (n & 1)) {
190 return nullptr; /* empty or odd number of digits */
191 }
192 n /= 2;
193 array->value = p = (char *)malloc(n + 1);
194
195 for (s1 = string; n; s1 += 2, n--) {
196 *p++ = xtoi_2(s1);
197 }
198 *p = 0;
199 } else {
200 /* regular v3 quoted string */
201 for (n = 0, s = string; *s; s++) {
202 if (*s == '\\') {
203 /* pair */
204 s++;
205 if (*s == ',' || *s == '=' || *s == '+' || *s == '<' || *s == '>' || *s == '#' || *s == ';' || *s == '\\' || *s == '\"' || *s == ' ') {
206 n++;
207 } else if (hexdigitp(s) && hexdigitp(s + 1)) {
208 s++;
209 n++;
210 } else {
211 return nullptr; /* invalid escape sequence */
212 }
213 } else if (*s == '\"') {
214 return nullptr; /* invalid encoding */
215 } else if (*s == ',' || *s == '=' || *s == '+' || *s == '<' || *s == '>' || *s == '#' || *s == ';') {
216 break;
217 } else {
218 n++;
219 }
220 }
221
222 array->value = p = (char *)malloc(n + 1);
223
224 for (s = string; n; s++, n--) {
225 if (*s == '\\') {
226 s++;
227 if (hexdigitp(s)) {
228 *p++ = xtoi_2(s);
229 s++;
230 } else {
231 *p++ = *s;
232 }
233 } else {
234 *p++ = *s;
235 }
236 }
237 *p = 0;
238 }
239 return s;
240}
241
242/* Parse a DN and return an array-ized one. This is not a validating
243 parser and it does not support any old-stylish syntax; gpgme is
244 expected to return only rfc2253 compatible strings. */
245static Kleo::DN::Attribute::List parse_dn(const unsigned char *string)
246{
247 if (!string) {
249 }
250
252 while (*string) {
253 while (*string == ' ') {
254 string++;
255 }
256 if (!*string) {
257 break; /* ready */
258 }
259
260 DnPair pair = {nullptr, nullptr};
261 string = parse_dn_part(&pair, string);
262 if (!string) {
263 goto failure;
264 }
265 if (pair.key && pair.value) {
266 result.push_back(Kleo::DN::Attribute(QString::fromUtf8(pair.key), QString::fromUtf8(pair.value)));
267 }
268 free(pair.key);
269 free(pair.value);
270
271 while (*string == ' ') {
272 string++;
273 }
274 if (*string && *string != ',' && *string != ';' && *string != '+') {
275 goto failure; /* invalid delimiter */
276 }
277 if (*string) {
278 string++;
279 }
280 }
281 return result;
282
283failure:
285}
286
287static QList<Kleo::DN::Attribute> parse_dn(const QString &dn)
288{
289 return parse_dn((const unsigned char *)dn.toUtf8().data());
290}
291
292static QString dn_escape(const QString &s)
293{
294 QString result;
295 for (int i = 0, end = s.length(); i != end; ++i) {
296 const QChar ch = s[i];
297 switch (ch.unicode()) {
298 case ',':
299 case '+':
300 case '"':
301 case '\\':
302 case '<':
303 case '>':
304 case ';':
305 result += QLatin1Char('\\');
306 // fall through
307 [[fallthrough]];
308 default:
309 result += ch;
310 }
311 }
312 return result;
313}
314
315static QStringList listAttributes(const QList<Kleo::DN::Attribute> &dn)
316{
317 QStringList result;
318 result.reserve(dn.size());
319 for (const auto &attribute : dn) {
320 if (!attribute.name().isEmpty() && !attribute.value().isEmpty()) {
321 result.push_back(attribute.name().trimmed() + QLatin1Char('=') + dn_escape(attribute.value().trimmed()));
322 }
323 }
324 return result;
325}
326
327static QString serialise(const QList<Kleo::DN::Attribute> &dn, const QString &sep)
328{
329 return listAttributes(dn).join(sep);
330}
331
332static Kleo::DN::Attribute::List reorder_dn(const Kleo::DN::Attribute::List &dn)
333{
334 const QStringList &attrOrder = Kleo::DN::attributeOrder();
335
336 Kleo::DN::Attribute::List unknownEntries;
338 unknownEntries.reserve(dn.size());
339 result.reserve(dn.size());
340
341 // find all unknown entries in their order of appearance
342 for (Kleo::DN::const_iterator it = dn.begin(); it != dn.end(); ++it) {
343 if (!attrOrder.contains((*it).name())) {
344 unknownEntries.push_back(*it);
345 }
346 }
347
348 // process the known attrs in the desired order
349 for (QStringList::const_iterator oit = attrOrder.begin(); oit != attrOrder.end(); ++oit) {
350 if (*oit == QLatin1StringView("_X_")) {
351 // insert the unknown attrs
352 std::copy(unknownEntries.begin(), unknownEntries.end(), std::back_inserter(result));
353 unknownEntries.clear(); // don't produce dup's
354 } else {
355 for (Kleo::DN::const_iterator dnit = dn.begin(); dnit != dn.end(); ++dnit) {
356 if ((*dnit).name() == *oit) {
357 result.push_back(*dnit);
358 }
359 }
360 }
361 }
362
363 return result;
364}
365
366//
367//
368// class DN
369//
370//
371
372Kleo::DN::DN()
373{
374 d = new Private();
375 d->ref();
376}
377
378Kleo::DN::DN(const QString &dn)
379{
380 d = new Private();
381 d->ref();
382 d->attributes = parse_dn(dn);
383}
384
385Kleo::DN::DN(const char *utf8DN)
386{
387 d = new Private();
388 d->ref();
389 if (utf8DN) {
390 d->attributes = parse_dn((const unsigned char *)utf8DN);
391 }
392}
393
394Kleo::DN::DN(const DN &other)
395 : d(other.d)
396{
397 if (d) {
398 d->ref();
399 }
400}
401
402Kleo::DN::~DN()
403{
404 if (d) {
405 d->unref();
406 }
407}
408
409const Kleo::DN &Kleo::DN::operator=(const DN &that)
410{
411 if (this->d == that.d) {
412 return *this;
413 }
414
415 if (that.d) {
416 that.d->ref();
417 }
418 if (this->d) {
419 this->d->unref();
420 }
421
422 this->d = that.d;
423
424 return *this;
425}
426
427// static
428QStringList Kleo::DN::attributeOrder()
429{
430 return DNAttributeOrderStore::instance()->attributeOrder();
431}
432
433// static
434void Kleo::DN::setAttributeOrder(const QStringList &order)
435{
436 DNAttributeOrderStore::instance()->setAttributeOrder(order);
437}
438
439// static
440QStringList Kleo::DN::defaultAttributeOrder()
441{
442 return defaultOrder;
443}
444
446{
447 if (!d) {
448 return QString();
449 }
450 if (d->reorderedAttributes.empty()) {
451 d->reorderedAttributes = reorder_dn(d->attributes);
452 }
453 return serialise(d->reorderedAttributes, QStringLiteral(","));
454}
455
457{
458 if (!d) {
459 return {};
460 }
461
462 if (d->reorderedAttributes.empty()) {
463 d->reorderedAttributes = reorder_dn(d->attributes);
464 }
465 return listAttributes(d->reorderedAttributes);
466}
467
469{
470 return d ? serialise(d->attributes, QStringLiteral(",")) : QString();
471}
472
473QString Kleo::DN::dn(const QString &sep) const
474{
475 return d ? serialise(d->attributes, sep) : QString();
476}
477
478// static
480{
481 return dn_escape(value);
482}
483
484void Kleo::DN::detach()
485{
486 if (!d) {
487 d = new Kleo::DN::Private();
488 d->ref();
489 } else if (d->refCount() > 1) {
490 Kleo::DN::Private *d_save = d;
491 d = new Kleo::DN::Private(*d);
492 d->ref();
493 d_save->unref();
494 }
495}
496
497void Kleo::DN::append(const Attribute &attr)
498{
499 detach();
500 d->attributes.push_back(attr);
501 d->reorderedAttributes.clear();
502}
503
504QString Kleo::DN::operator[](const QString &attr) const
505{
506 if (!d) {
507 return QString();
508 }
509 const QString attrUpper = attr.toUpper();
510 for (QList<Attribute>::const_iterator it = d->attributes.constBegin(); it != d->attributes.constEnd(); ++it) {
511 if ((*it).name() == attrUpper) {
512 return (*it).value();
513 }
514 }
515 return QString();
516}
517
518static QList<Kleo::DN::Attribute> empty;
519
520Kleo::DN::const_iterator Kleo::DN::begin() const
521{
522 return d ? d->attributes.constBegin() : empty.constBegin();
523}
524
525Kleo::DN::const_iterator Kleo::DN::end() const
526{
527 return d ? d->attributes.constEnd() : empty.constEnd();
528}
529
530/////////////////////
531
532namespace
533{
534static const QMap<QString, KLazyLocalizedString> attributeNamesAndLabels = {
535 // clang-format off
536 {QStringLiteral("CN"), kli18n("Common name") },
537 {QStringLiteral("SN"), kli18n("Surname") },
538 {QStringLiteral("GN"), kli18n("Given name") },
539 {QStringLiteral("L"), kli18n("Location") },
540 {QStringLiteral("T"), kli18n("Title") },
541 {QStringLiteral("OU"), kli18n("Organizational unit")},
542 {QStringLiteral("O"), kli18n("Organization") },
543 {QStringLiteral("PC"), kli18n("Postal code") },
544 {QStringLiteral("C"), kli18n("Country code") },
545 {QStringLiteral("SP"), kli18n("State or province") },
546 {QStringLiteral("DC"), kli18n("Domain component") },
547 {QStringLiteral("BC"), kli18n("Business category") },
548 {QStringLiteral("EMAIL"), kli18n("Email address") },
549 {QStringLiteral("MAIL"), kli18n("Mail address") },
550 {QStringLiteral("MOBILE"), kli18n("Mobile phone number")},
551 {QStringLiteral("TEL"), kli18n("Telephone number") },
552 {QStringLiteral("FAX"), kli18n("Fax number") },
553 {QStringLiteral("STREET"), kli18n("Street address") },
554 {QStringLiteral("UID"), kli18n("Unique ID") },
555 // clang-format on
556};
557}
558
559// static
560QStringList Kleo::DN::attributeNames()
561{
562 return attributeNamesAndLabels.keys();
563}
564
565// static
566QString Kleo::DN::attributeNameToLabel(const QString &name)
567{
568 const QString key{name.trimmed().toUpper()};
569 if (attributeNames().contains(key)) {
570 return attributeNamesAndLabels.value(key).toString();
571 }
572 qCWarning(LIBKLEO_LOG) << "Attribute " << key << " doesn't exit. Bug ?";
573 return {};
574}
DN parser and reorderer.
Definition dn.h:27
static QString escape(const QString &value)
Definition dn.cpp:479
QString prettyDN() const
Definition dn.cpp:445
QString dn() const
Definition dn.cpp:468
QStringList prettyAttributes() const
Returns the non-empty attributes formatted as {NAME=value} and reordered according to the settings in...
Definition dn.cpp:456
char * data()
char16_t & unicode()
iterator begin()
void clear()
bool empty() const const
iterator end()
void push_back(parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
QString fromUtf8(QByteArrayView str)
qsizetype length() const const
QString toUpper() const const
QByteArray toUtf8() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:29:01 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.