File indexing completed on 2024-04-28 15:51:42

0001 /*
0002     SPDX-FileCopyrightText: 2002 g10 Code GmbH
0003     SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
0004     SPDX-FileCopyrightText: 2021 g10 Code GmbH
0005     SPDX-FileCopyrightText: 2023 g10 Code GmbH
0006     SPDX-FileContributor: Author: Sune Stolborg Vuorela <sune@vuorela.dk>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 
0010     Copied from poppler (pdf library)
0011 */
0012 
0013 #ifndef DISTINGUISHEDNAMEPARSER_H
0014 #define DISTINGUISHEDNAMEPARSER_H
0015 
0016 #include <algorithm>
0017 #include <optional>
0018 #include <string>
0019 #include <utility>
0020 #include <vector>
0021 
0022 namespace DN
0023 {
0024 namespace detail
0025 {
0026 
0027 inline std::string_view removeLeadingSpaces(std::string_view view)
0028 {
0029     auto pos = view.find_first_not_of(' ');
0030     if (pos > view.size()) {
0031         return {};
0032     }
0033     return view.substr(pos);
0034 }
0035 
0036 inline std::string_view removeTrailingSpaces(std::string_view view)
0037 {
0038     auto pos = view.find_last_not_of(' ');
0039     if (pos > view.size()) {
0040         return {};
0041     }
0042     return view.substr(0, pos + 1);
0043 }
0044 
0045 inline unsigned char xtoi(unsigned char c)
0046 {
0047     if (c <= '9') {
0048         return c - '0';
0049     }
0050     if (c <= 'F') {
0051         return c - 'A' + 10;
0052     }
0053     return c < 'a' + 10;
0054 }
0055 
0056 inline unsigned char xtoi(unsigned char first, unsigned char second)
0057 {
0058     return 16 * xtoi(first) + xtoi(second);
0059 }
0060 // Parses a hex string into actual content
0061 inline std::optional<std::string> parseHexString(std::string_view view)
0062 {
0063     auto size = view.size();
0064     if (size == 0 || (size % 2 == 1)) {
0065         return std::nullopt;
0066     }
0067     // It is only supposed to be called with actual hex strings
0068     // but this is just to be extra sure
0069     auto endHex = view.find_first_not_of("1234567890abcdefABCDEF");
0070     if (endHex != std::string_view::npos) {
0071         return {};
0072     }
0073     std::string result;
0074     result.reserve(size / 2);
0075     for (size_t i = 0; i < (view.size() - 1); i += 2) {
0076         result.push_back(xtoi(view[i], view[i + 1]));
0077     }
0078     return result;
0079 }
0080 
0081 static const std::vector<std::pair<std::string_view, std::string_view>> &oidmap()
0082 {
0083     static const std::vector<std::pair<std::string_view, std::string_view>> oidmap_ = {
0084         // clang-format off
0085     // keep them ordered by oid:
0086     {"NameDistinguisher", "0.2.262.1.10.7.20"   },
0087     {"EMAIL",             "1.2.840.113549.1.9.1"},
0088     {"CN",                "2.5.4.3"             },
0089     {"SN",                "2.5.4.4"             },
0090     {"SerialNumber",      "2.5.4.5"             },
0091     {"T",                 "2.5.4.12"            },
0092     {"D",                 "2.5.4.13"            },
0093     {"BC",                "2.5.4.15"            },
0094     {"ADDR",              "2.5.4.16"            },
0095     {"PC",                "2.5.4.17"            },
0096     {"GN",                "2.5.4.42"            },
0097     {"Pseudo",            "2.5.4.65"            },
0098         // clang-format on
0099     };
0100     return oidmap_;
0101 }
0102 
0103 static std::string_view attributeNameForOID(std::string_view oid)
0104 {
0105     if (oid.substr(0, 4) == std::string_view {"OID."} || oid.substr(0, 4) == std::string_view {"oid."}) { // c++20 has starts_with. we don't have that yet.
0106         oid.remove_prefix(4);
0107     }
0108     for (const auto &m : oidmap()) {
0109         if (oid == m.second) {
0110             return m.first;
0111         }
0112     }
0113     return {};
0114 }
0115 
0116 /* Parse a DN and return an array-ized one.  This is not a validating
0117    parser and it does not support any old-stylish syntax; gpgme is
0118    expected to return only rfc2253 compatible strings. */
0119 static std::pair<std::optional<std::string_view>, std::pair<std::string, std::string>> parse_dn_part(std::string_view stringv)
0120 {
0121     std::pair<std::string, std::string> dnPair;
0122     auto separatorPos = stringv.find_first_of('=');
0123     if (separatorPos == 0 || separatorPos == std::string_view::npos) {
0124         return {}; /* empty key */
0125     }
0126 
0127     std::string_view key = stringv.substr(0, separatorPos);
0128     key = removeTrailingSpaces(key);
0129     // map OIDs to their names:
0130     if (auto name = attributeNameForOID(key); !name.empty()) {
0131         key = name;
0132     }
0133 
0134     dnPair.first = std::string {key};
0135     stringv = removeLeadingSpaces(stringv.substr(separatorPos + 1));
0136     if (stringv.empty()) {
0137         return {};
0138     }
0139 
0140     if (stringv.front() == '#') {
0141         /* hexstring */
0142         stringv.remove_prefix(1);
0143         auto endHex = stringv.find_first_not_of("1234567890abcdefABCDEF");
0144         if (!endHex || (endHex % 2 == 1)) {
0145             return {}; /* empty or odd number of digits */
0146         }
0147         auto value = parseHexString(stringv.substr(0, endHex));
0148         if (!value.has_value()) {
0149             return {};
0150         }
0151         stringv = stringv.substr(endHex);
0152         dnPair.second = value.value();
0153     } else if (stringv.front() == '"') {
0154         stringv.remove_prefix(1);
0155         std::string value;
0156         bool stop = false;
0157         while (!stringv.empty() && !stop) {
0158             switch (stringv.front()) {
0159             case '\\': {
0160                 if (stringv.size() < 2) {
0161                     return {};
0162                 }
0163                 if (stringv[1] == '"') {
0164                     value.push_back('"');
0165                     stringv.remove_prefix(2);
0166                 } else {
0167                     // it is a bit unclear in rfc2253 if escaped hex chars should
0168                     // be decoded inside quotes. Let's just forward the verbatim
0169                     // for now
0170                     value.push_back(stringv.front());
0171                     value.push_back(stringv[1]);
0172                     stringv.remove_prefix(2);
0173                 }
0174                 break;
0175             }
0176             case '"': {
0177                 stop = true;
0178                 stringv.remove_prefix(1);
0179                 break;
0180             }
0181             default: {
0182                 value.push_back(stringv.front());
0183                 stringv.remove_prefix(1);
0184             }
0185             }
0186         }
0187         if (!stop) {
0188             // we have reached end of string, but never an actual ", so error out
0189             return {};
0190         }
0191         dnPair.second = value;
0192     } else {
0193         std::string value;
0194         bool stop = false;
0195         bool lastAddedEscapedSpace = false;
0196         while (!stringv.empty() && !stop) {
0197             switch (stringv.front()) {
0198             case '\\': //_escaping
0199             {
0200                 stringv.remove_prefix(1);
0201                 if (stringv.empty()) {
0202                     return {};
0203                 }
0204                 switch (stringv.front()) {
0205                 case ',':
0206                 case '=':
0207                 case '+':
0208                 case '<':
0209                 case '>':
0210                 case '#':
0211                 case ';':
0212                 case '\\':
0213                 case '"':
0214                 case ' ': {
0215                     if (stringv.front() == ' ') {
0216                         lastAddedEscapedSpace = true;
0217                     } else {
0218                         lastAddedEscapedSpace = false;
0219                     }
0220                     value.push_back(stringv.front());
0221                     stringv.remove_prefix(1);
0222                     break;
0223                 }
0224                 default: {
0225                     if (stringv.size() < 2) {
0226                         // this should be double hex-ish, but isn't.
0227                         return {};
0228                     }
0229                     if (std::isxdigit(stringv.front()) && std::isxdigit(stringv[1])) {
0230                         lastAddedEscapedSpace = false;
0231                         value.push_back(xtoi(stringv.front(), stringv[1]));
0232                         stringv.remove_prefix(2);
0233                         break;
0234                     } else {
0235                         // invalid escape
0236                         return {};
0237                     }
0238                 }
0239                 }
0240                 break;
0241             }
0242             case '"':
0243                 // unescaped " in the middle; not allowed
0244                 return {};
0245             case ',':
0246             case '=':
0247             case '+':
0248             case '<':
0249             case '>':
0250             case '#':
0251             case ';': {
0252                 stop = true;
0253                 break; //
0254             }
0255             default:
0256                 lastAddedEscapedSpace = false;
0257                 value.push_back(stringv.front());
0258                 stringv.remove_prefix(1);
0259             }
0260         }
0261         if (lastAddedEscapedSpace) {
0262             dnPair.second = value;
0263         } else {
0264             dnPair.second = std::string {removeTrailingSpaces(value)};
0265         }
0266     }
0267     return {stringv, dnPair};
0268 }
0269 }
0270 
0271 using Result = std::vector<std::pair<std::string, std::string>>;
0272 
0273 /* Parse a DN and return an array-ized one.  This is not a validating
0274    parser and it does not support any old-stylish syntax; gpgme is
0275    expected to return only rfc2253 compatible strings. */
0276 static Result parseString(std::string_view string)
0277 {
0278     Result result;
0279     while (!string.empty()) {
0280         string = detail::removeLeadingSpaces(string);
0281         if (string.empty()) {
0282             break;
0283         }
0284 
0285         auto [partResult, dnPair] = detail::parse_dn_part(string);
0286         if (!partResult.has_value()) {
0287             return {};
0288         }
0289 
0290         string = partResult.value();
0291         if (dnPair.first.size() && dnPair.second.size()) {
0292             result.emplace_back(std::move(dnPair));
0293         }
0294 
0295         string = detail::removeLeadingSpaces(string);
0296         if (string.empty()) {
0297             break;
0298         }
0299         switch (string.front()) {
0300         case ',':
0301         case ';':
0302         case '+':
0303             string.remove_prefix(1);
0304             break;
0305         default:
0306             // some unexpected characters here
0307             return {};
0308         }
0309     }
0310     return result;
0311 }
0312 
0313 /// returns the first value of a given key (note. there can be multiple)
0314 /// or nullopt if key is not available
0315 inline std::optional<std::string> FindFirstValue(const Result &dn, std::string_view key)
0316 {
0317     auto first = std::find_if(dn.begin(), dn.end(), [&key](const auto &it) { return it.first == key; });
0318     if (first == dn.end()) {
0319         return {};
0320     }
0321     return first->second;
0322 }
0323 } // namespace DN
0324 #endif // DISTINGUISHEDNAMEPARSER_H