1 //
2 // Copyright (c) ZeroC, Inc. All rights reserved.
3 //
4
5 #include <IceUtil/StringUtil.h>
6
7 #include <IceSSL/Plugin.h>
8 #include <IceSSL/RFC2253.h>
9 #include <Ice/Object.h>
10
11 using namespace std;
12 using namespace IceSSL;
13
14 namespace
15 {
16
17 //
18 // See RFC 2253 and RFC 1779.
19 //
20
21 string special = ",=+<>#;";
22 string hexvalid = "0123456789abcdefABCDEF";
23
24 }
25
26 static char unescapeHex(const string&, size_t);
27 static pair<string,string> parseNameComponent(const string&, size_t&);
28 static pair<string,string> parseAttributeTypeAndValue(const string&, size_t&);
29 static string parseAttributeType(const string&, size_t&);
30 static string parseAttributeValue(const string&, size_t&);
31 static string parsePair(const string&, size_t&);
32 static string parseHexPair(const string&, size_t&, bool);
33 static void eatWhite(const string&, size_t&);
34
35 RFC2253::RDNEntrySeq
parse(const string & data)36 RFC2253::parse(const string& data)
37 {
38 RDNEntrySeq results;
39 RDNEntry current;
40 current.negate = false;
41 size_t pos = 0;
42 while(pos < data.size())
43 {
44 eatWhite(data, pos);
45 if(pos < data.size() && data[pos] == '!')
46 {
47 if(!current.rdn.empty())
48 {
49 throw ParseException(__FILE__, __LINE__, "negation symbol '!' must appear at start of list");
50 }
51 ++pos;
52 current.negate = true;
53 }
54 current.rdn.push_back(parseNameComponent(data, pos));
55 eatWhite(data, pos);
56 if(pos < data.size() && data[pos] == ',')
57 {
58 ++pos;
59 }
60 else if(pos < data.size() && data[pos] == ';')
61 {
62 ++pos;
63 results.push_back(current);
64 current.rdn.clear();
65 current.negate = false;
66 }
67 else if(pos < data.size())
68 {
69 throw ParseException(__FILE__, __LINE__, "expected ',' or ';' at `" + data.substr(pos) + "'");
70 }
71 }
72 if(!current.rdn.empty())
73 {
74 results.push_back(current);
75 }
76
77 return results;
78 }
79
80 RFC2253::RDNSeq
parseStrict(const string & data)81 RFC2253::parseStrict(const string& data)
82 {
83 RDNSeq results;
84 size_t pos = 0;
85 while(pos < data.size())
86 {
87 results.push_back(parseNameComponent(data, pos));
88 eatWhite(data, pos);
89 if(pos < data.size() && (data[pos] == ',' || data[pos] == ';'))
90 {
91 ++pos;
92 }
93 else if(pos < data.size())
94 {
95 throw ParseException(__FILE__, __LINE__, "expected ',' or ';' at `" + data.substr(pos) + "'");
96 }
97 }
98 return results;
99 }
100
101 string
unescape(const string & data)102 RFC2253::unescape(const string& data)
103 {
104 if(data.size() == 0)
105 {
106 return data;
107 }
108
109 if(data[0] == '"')
110 {
111 if(data[data.size() - 1] != '"')
112 {
113 throw ParseException(__FILE__, __LINE__, "unescape: missing \"");
114 }
115
116 //
117 // Return the string without quotes.
118 //
119 return data.substr(1, data.size() - 2);
120 }
121
122 //
123 // Unescape the entire string.
124 //
125 string result;
126 if(data[0] == '#')
127 {
128 size_t pos = 1;
129 while(pos < data.size())
130 {
131 result += unescapeHex(data, pos);
132 pos += 2;
133 }
134 }
135 else
136 {
137 size_t pos = 0;
138 while(pos < data.size())
139 {
140 if(data[pos] != '\\')
141 {
142 result += data[pos];
143 ++pos;
144 }
145 else
146 {
147 ++pos;
148 if(pos >= data.size())
149 {
150 throw ParseException(__FILE__, __LINE__, "unescape: invalid escape sequence");
151 }
152 if(special.find(data[pos]) != string::npos || data[pos] != '\\' || data[pos] != '"')
153 {
154 result += data[pos];
155 ++pos;
156 }
157 else
158 {
159 result += unescapeHex(data, pos);
160 pos += 2;
161 }
162 }
163 }
164 }
165
166 return result;
167 }
168
169 static int
hexToInt(char v)170 hexToInt(char v)
171 {
172 if(v >= '0' && v <= '9')
173 {
174 return v - '0';
175 }
176 if(v >= 'a' && v <= 'f')
177 {
178 return 10 + (v - 'a');
179 }
180 if(v >= 'A' && v <= 'F')
181 {
182 return 10 + (v - 'A');
183 }
184 throw ParseException(__FILE__, __LINE__, "unescape: invalid hex pair");
185 }
186
187 static char
unescapeHex(const string & data,size_t pos)188 unescapeHex(const string& data, size_t pos)
189 {
190 assert(pos < data.size());
191 if(pos + 2 >= data.size())
192 {
193 throw ParseException(__FILE__, __LINE__, "unescape: invalid hex pair");
194 }
195 return static_cast<char>(hexToInt(data[pos]) * 16 + hexToInt(data[pos + 1]));
196 }
197
198 static pair<string,string>
parseNameComponent(const string & data,size_t & pos)199 parseNameComponent(const string& data, size_t& pos)
200 {
201 pair<string, string> final = parseAttributeTypeAndValue(data, pos);
202 while(pos < data.size())
203 {
204 eatWhite(data, pos);
205 if(pos < data.size() && data[pos] == '+')
206 {
207 ++pos;
208 }
209 else
210 {
211 break;
212 }
213 pair<string, string> p = parseAttributeTypeAndValue(data, pos);
214 final.second += "+";
215 final.second += p.first;
216 final.second += '=';
217 final.second += p.second;
218 }
219 return final;
220 }
221
222 static pair<string,string>
parseAttributeTypeAndValue(const string & data,size_t & pos)223 parseAttributeTypeAndValue(const string& data, size_t& pos)
224 {
225 pair<string, string> p;
226 p.first = parseAttributeType(data, pos);
227 eatWhite(data, pos);
228
229 if(pos >= data.size())
230 {
231 throw ParseException(__FILE__, __LINE__, "invalid attribute type/value pair (unexpected end of data)");
232 }
233 if(data[pos] != '=')
234 {
235 throw ParseException(__FILE__, __LINE__, "invalid attribute type/value pair (missing =)");
236 }
237 ++pos;
238 p.second = parseAttributeValue(data, pos);
239 return p;
240 }
241
242 static string
parseAttributeType(const string & data,size_t & pos)243 parseAttributeType(const string& data, size_t& pos)
244 {
245 eatWhite(data, pos);
246 if(pos >= data.size())
247 {
248 throw ParseException(__FILE__, __LINE__, "invalid attribute type (expected end of data)");
249 }
250
251 string result;
252
253 //
254 // RFC 1779.
255 // <key> ::= 1*( <keychar> ) | "OID." <oid> | "oid." <oid>
256 // <oid> ::= <digitstring> | <digitstring> "." <oid>
257 // RFC 2253:
258 // attributeType = (ALPHA 1*keychar) | oid
259 // keychar = ALPHA | DIGIT | "-"
260 // oid = 1*DIGIT *("." 1*DIGIT)
261 //
262 // In section 4 of RFC 2253 the document says:
263 // Implementations MUST allow an oid in the attribute type to be
264 // prefixed by one of the character strings "oid." or "OID.".
265 //
266 // Here we must also check for "oid." and "OID." before parsing
267 // according to the ALPHA KEYCHAR* rule.
268 //
269 // First the OID case.
270 //
271 if(IceUtilInternal::isDigit(data[pos]) ||
272 (data.size() - pos >= 4 && (data.substr(pos, 4) == "oid." || data.substr(pos, 4) == "OID.")))
273 {
274 if(!IceUtilInternal::isDigit(data[pos]))
275 {
276 result += data.substr(pos, 4);
277 pos += 4;
278 }
279
280 while(true)
281 {
282 // 1*DIGIT
283 while(pos < data.size() && IceUtilInternal::isDigit(data[pos]))
284 {
285 result += data[pos];
286 ++pos;
287 }
288 // "." 1*DIGIT
289 if(pos < data.size() && data[pos] == '.')
290 {
291 result += data[pos];
292 ++pos;
293 // 1*DIGIT must follow "."
294 if(pos < data.size() && !IceUtilInternal::isDigit(data[pos]))
295 {
296 throw ParseException(__FILE__, __LINE__, "invalid attribute type (expected end of data)");
297 }
298 }
299 else
300 {
301 break;
302 }
303 }
304 }
305 else if(IceUtilInternal::isAlpha(data[pos]))
306 {
307 //
308 // The grammar is wrong in this case. It should be ALPHA
309 // KEYCHAR* otherwise it will not accept "O" as a valid
310 // attribute type.
311 //
312 result += data[pos];
313 ++pos;
314 // 1* KEYCHAR
315 while(pos < data.size() &&
316 (IceUtilInternal::isAlpha(data[pos]) || IceUtilInternal::isDigit(data[pos]) || data[pos] == '-'))
317 {
318 result += data[pos];
319 ++pos;
320 }
321 }
322 else
323 {
324 throw ParseException(__FILE__, __LINE__, "invalid attribute type");
325 }
326 return result;
327 }
328
329 static string
parseAttributeValue(const string & data,size_t & pos)330 parseAttributeValue(const string& data, size_t& pos)
331 {
332 eatWhite(data, pos);
333 string result;
334 if(pos >= data.size())
335 {
336 return result;
337 }
338
339 //
340 // RFC 2253
341 // # hexstring
342 //
343 if(data[pos] == '#')
344 {
345 result += data[pos];
346 ++pos;
347 while(true)
348 {
349 string h = parseHexPair(data, pos, true);
350 if(h.size() == 0)
351 {
352 break;
353 }
354 result += h;
355 }
356 }
357 //
358 // RFC 2253
359 // QUOTATION *( quotechar | pair ) QUOTATION ; only from v2
360 // quotechar = <any character except "\" or QUOTATION >
361 //
362 else if(data[pos] == '"')
363 {
364 result += data[pos];
365 ++pos;
366 while(true)
367 {
368 if(pos >= data.size())
369 {
370 throw ParseException(__FILE__, __LINE__, "invalid attribute value (unexpected end of data)");
371 }
372 // final terminating "
373 if(data[pos] == '"')
374 {
375 result += data[pos];
376 ++pos;
377 break;
378 }
379 // any character except '\'
380 else if(data[pos] != '\\')
381 {
382 result += data[pos];
383 ++pos;
384 }
385 // pair '\'
386 else
387 {
388 result += parsePair(data, pos);
389 }
390 }
391 }
392 //
393 // RFC 2253
394 // * (stringchar | pair)
395 // stringchar = <any character except one of special, "\" or QUOTATION >
396 //
397 else
398 {
399 while(pos < data.size())
400 {
401 if(data[pos] == '\\')
402 {
403 result += parsePair(data, pos);
404 }
405 else if(special.find(data[pos]) == string::npos && data[pos] != '"')
406 {
407 result += data[pos];
408 ++pos;
409 }
410 else
411 {
412 break;
413 }
414 }
415 }
416 return result;
417 }
418
419 //
420 // RFC2253:
421 // pair = "\" ( special | "\" | QUOTATION | hexpair )
422 //
423 static string
parsePair(const string & data,size_t & pos)424 parsePair(const string& data, size_t& pos)
425 {
426 string result;
427
428 assert(data[pos] == '\\');
429 result += data[pos];
430 ++pos;
431
432 if(pos >= data.size())
433 {
434 throw ParseException(__FILE__, __LINE__, "invalid escape format (unexpected end of data)");
435 }
436
437 if(special.find(data[pos]) != string::npos || data[pos] != '\\' || data[pos] != '"')
438 {
439 result += data[pos];
440 ++pos;
441 return result;
442 }
443 return parseHexPair(data, pos, false);
444 }
445
446 //
447 // RFC 2253
448 // hexpair = hexchar hexchar
449 //
450 static string
parseHexPair(const string & data,size_t & pos,bool allowEmpty)451 parseHexPair(const string& data, size_t& pos, bool allowEmpty)
452 {
453 string result;
454 if(pos < data.size() && hexvalid.find(data[pos]) != string::npos)
455 {
456 result += data[pos];
457 ++pos;
458 }
459 if(pos < data.size() && hexvalid.find(data[pos]) != string::npos)
460 {
461 result += data[pos];
462 ++pos;
463 }
464 if(result.size() != 2)
465 {
466 if(allowEmpty && result.size() == 0)
467 {
468 return result;
469 }
470 throw ParseException(__FILE__, __LINE__, "invalid hex format");
471 }
472 return result;
473 }
474
475 //
476 // RFC 2253:
477 //
478 // Implementations MUST allow for space (' ' ASCII 32) characters to be
479 // present between name-component and ',', between attributeTypeAndValue
480 // and '+', between attributeType and '=', and between '=' and
481 // attributeValue. These space characters are ignored when parsing.
482 //
483 static void
eatWhite(const string & data,size_t & pos)484 eatWhite(const string& data, size_t& pos)
485 {
486 while(pos < data.size() && data[pos] == ' ')
487 {
488 ++pos;
489 }
490 }
491