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