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