1 // $OpenLDAP$
2 /*
3 * Copyright 2000-2021 The OpenLDAP Foundation, All Rights Reserved.
4 * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
5 */
6
7
8 #include "LDAPUrl.h"
9 #include <sstream>
10 #include <iomanip>
11 #include "debug.h"
12
13 using namespace std;
14
15 #define PCT_ENCFLAG_NONE 0x0000U
16 #define PCT_ENCFLAG_COMMA 0x0001U
17 #define PCT_ENCFLAG_SLASH 0x0002U
18
19 #define LDAP_DEFAULT_PORT 389
20 #define LDAPS_DEFAULT_PORT 636
21
LDAPUrl(const std::string & url)22 LDAPUrl::LDAPUrl(const std::string &url)
23 {
24 DEBUG(LDAP_DEBUG_CONSTRUCT, "LDAPUrl::LDAPUrl()" << endl);
25 DEBUG(LDAP_DEBUG_CONSTRUCT | LDAP_DEBUG_PARAMETER,
26 " url:" << url << endl);
27 m_urlString = url;
28 m_Filter = "";
29 m_Scheme = "ldap";
30 m_Scope = 0;
31 m_Port = 0;
32 regenerate = false;
33 if (url != "") {
34 this->parseUrl();
35 }
36 }
37
~LDAPUrl()38 LDAPUrl::~LDAPUrl()
39 {
40 DEBUG(LDAP_DEBUG_DESTROY, "LDAPUrl::~LDAPUrl()" << endl);
41 m_Attrs.clear();
42 }
43
getPort() const44 int LDAPUrl::getPort() const
45 {
46 return m_Port;
47 }
48
setPort(int port)49 void LDAPUrl::setPort(int port)
50 {
51 m_Port = port;
52 regenerate = true;
53 }
54
getScope() const55 int LDAPUrl::getScope() const
56 {
57 return m_Scope;
58 }
59
setScope(const std::string & scope)60 void LDAPUrl::setScope( const std::string &scope )
61 {
62 if (scope == "base" || scope == "" ) {
63 m_Scope = 0;
64 } else if (scope == "one" ) {
65 m_Scope = 1;
66 } else if (scope == "sub" ) {
67 m_Scope = 2;
68 } else {
69 throw LDAPUrlException(LDAPUrlException::INVALID_SCOPE,
70 "Scope was:" + scope);
71 }
72 regenerate = true;
73 }
74
getURLString() const75 const string& LDAPUrl::getURLString() const
76 {
77 if (regenerate){
78 this->components2Url();
79 regenerate=false;
80 }
81 return m_urlString;
82 }
83
setURLString(const std::string & url)84 void LDAPUrl::setURLString( const std::string &url )
85 {
86 m_urlString = url;
87 if (url != "") {
88 this->parseUrl();
89 }
90 regenerate = false;
91 }
92
getHost() const93 const string& LDAPUrl::getHost() const
94 {
95 return m_Host;
96 }
97
setHost(const std::string & host)98 void LDAPUrl::setHost( const std::string &host )
99 {
100 m_Host = host;
101 regenerate = true;
102 }
103
getDN() const104 const string& LDAPUrl::getDN() const
105 {
106 return m_DN;
107 }
setDN(const std::string & dn)108 void LDAPUrl::setDN( const std::string &dn )
109 {
110 m_DN = dn;
111 regenerate = true;
112 }
113
getFilter() const114 const string& LDAPUrl::getFilter() const
115 {
116 return m_Filter;
117 }
setFilter(const std::string & filter)118 void LDAPUrl::setFilter( const std::string &filter )
119 {
120 m_Filter = filter;
121 regenerate = true;
122 }
123
getAttrs() const124 const StringList& LDAPUrl::getAttrs() const
125 {
126 return m_Attrs;
127 }
setAttrs(const StringList & attrs)128 void LDAPUrl::setAttrs( const StringList &attrs )
129 {
130 m_Attrs = attrs;
131 regenerate = true;
132 }
133
getExtensions() const134 const StringList& LDAPUrl::getExtensions() const
135 {
136 return m_Extensions;
137 }
138
setExtensions(const StringList & ext)139 void LDAPUrl::setExtensions( const StringList &ext )
140 {
141 m_Extensions = ext;
142 regenerate = true;
143 }
144
getScheme() const145 const std::string& LDAPUrl::getScheme() const
146 {
147 return m_Scheme;
148 }
149
setScheme(const std::string & scheme)150 void LDAPUrl::setScheme( const std::string &scheme )
151 {
152 if (scheme == "ldap" || scheme == "ldaps" ||
153 scheme == "ldapi" || scheme == "cldap" )
154 {
155 m_Scheme = scheme;
156 regenerate = true;
157 } else {
158 throw LDAPUrlException(LDAPUrlException::INVALID_SCHEME,
159 "Unknown URL scheme: \"" + scheme + "\"");
160 }
161 }
162
parseUrl()163 void LDAPUrl::parseUrl()
164 {
165 DEBUG(LDAP_DEBUG_TRACE, "LDAPUrl::parseUrl()" << std::endl);
166 // reading Scheme
167 std::string::size_type pos = m_urlString.find(':');
168 std::string::size_type startpos = pos;
169 if (pos == std::string::npos) {
170 throw LDAPUrlException(LDAPUrlException::INVALID_URL,
171 "No colon found in URL");
172 }
173 std::string scheme = m_urlString.substr(0, pos);
174 DEBUG(LDAP_DEBUG_TRACE, " scheme is <" << scheme << ">" << std::endl);
175
176 if ( scheme == "ldap" ) {
177 m_Scheme = scheme;
178 } else if ( scheme == "ldaps" ) {
179 m_Scheme = scheme;
180 } else if ( scheme == "ldapi" ) {
181 m_Scheme = scheme;
182 } else if ( scheme == "cldap" ) {
183 m_Scheme = scheme;
184 } else {
185 throw LDAPUrlException(LDAPUrlException::INVALID_SCHEME,
186 "Unknown URL Scheme: \"" + scheme + "\"");
187 }
188
189 if ( m_urlString[pos+1] != '/' || m_urlString[pos+2] != '/' ) {
190 throw LDAPUrlException(LDAPUrlException::INVALID_URL);
191 } else {
192 startpos = pos + 3;
193 }
194 if ( m_urlString[startpos] == '/' ) {
195 // no hostname and port
196 startpos++;
197 } else {
198 std::string::size_type hostend, portstart=0;
199 pos = m_urlString.find('/', startpos);
200
201 // IPv6 Address?
202 if ( m_urlString[startpos] == '[' ) {
203 // skip
204 startpos++;
205 hostend = m_urlString.find(']', startpos);
206 if ( hostend == std::string::npos ){
207 throw LDAPUrlException(LDAPUrlException::INVALID_URL);
208 }
209 portstart = hostend + 1;
210 } else {
211 hostend = m_urlString.find(':', startpos);
212 if ( hostend == std::string::npos || portstart > pos ) {
213 hostend = pos;
214 }
215 portstart = hostend;
216 }
217 std::string host = m_urlString.substr(startpos, hostend - startpos);
218 DEBUG(LDAP_DEBUG_TRACE, " host: <" << host << ">" << std::endl);
219 percentDecode(host, m_Host);
220
221 if (portstart >= m_urlString.length() || portstart >= pos ) {
222 if ( m_Scheme == "ldap" || m_Scheme == "cldap" ) {
223 m_Port = LDAP_DEFAULT_PORT;
224 } else if ( m_Scheme == "ldaps" ) {
225 m_Port = LDAPS_DEFAULT_PORT;
226 }
227 } else {
228 std::string port = m_urlString.substr(portstart+1,
229 (pos == std::string::npos ? pos : pos-portstart-1) );
230 if ( port.length() > 0 ) {
231 std::istringstream i(port);
232 i >> m_Port;
233 if ( i.fail() ){
234 throw LDAPUrlException(LDAPUrlException::INVALID_PORT);
235 }
236 }
237 DEBUG(LDAP_DEBUG_TRACE, " Port: <" << m_Port << ">"
238 << std::endl);
239 }
240 startpos = pos + 1;
241 }
242 int parserMode = base;
243 while ( pos != std::string::npos ) {
244 pos = m_urlString.find('?', startpos);
245 std::string actComponent = m_urlString.substr(startpos,
246 pos - startpos);
247 DEBUG(LDAP_DEBUG_TRACE, " ParserMode:" << parserMode << std::endl);
248 DEBUG(LDAP_DEBUG_TRACE, " ActComponent: <" << actComponent << ">"
249 << std::endl);
250 std::string s_scope = "";
251 std::string s_ext = "";
252 switch(parserMode) {
253 case base :
254 percentDecode(actComponent, m_DN);
255 DEBUG(LDAP_DEBUG_TRACE, " BaseDN:" << m_DN << std::endl);
256 break;
257 case attrs :
258 DEBUG(LDAP_DEBUG_TRACE, " reading Attributes" << std::endl);
259 if (actComponent.length() != 0 ) {
260 string2list(actComponent,m_Attrs, true);
261 }
262 break;
263 case scope :
264 percentDecode(actComponent, s_scope);
265 if (s_scope == "base" || s_scope == "" ) {
266 m_Scope = 0;
267 } else if (s_scope == "one" ) {
268 m_Scope = 1;
269 } else if (s_scope == "sub" ) {
270 m_Scope = 2;
271 } else {
272 throw LDAPUrlException(LDAPUrlException::INVALID_SCOPE);
273 }
274 DEBUG(LDAP_DEBUG_TRACE, " Scope: <" << s_scope << ">"
275 << std::endl);
276 break;
277 case filter :
278 percentDecode(actComponent, m_Filter);
279 DEBUG(LDAP_DEBUG_TRACE, " filter: <" << m_Filter << ">"
280 << std::endl);
281 break;
282 case extensions :
283 DEBUG(LDAP_DEBUG_TRACE, " reading Extensions" << std::endl);
284 string2list(actComponent, m_Extensions, true);
285 break;
286 default :
287 DEBUG(LDAP_DEBUG_TRACE, " unknown state" << std::endl);
288 break;
289 }
290 startpos = pos + 1;
291 parserMode++;
292 }
293 }
294
percentDecode(const std::string & src,std::string & out)295 void LDAPUrl::percentDecode(const std::string& src, std::string &out)
296 {
297 DEBUG(LDAP_DEBUG_TRACE, "LDAPUrl::percentDecode()" << std::endl);
298 std::string::size_type pos = 0;
299 std::string::size_type startpos = 0;
300 pos = src.find('%', startpos);
301 while ( pos != std::string::npos ) {
302 out += src.substr(startpos, pos - startpos);
303 std::string istr(src.substr(pos+1, 2));
304 std::istringstream i(istr);
305 i.setf(std::ios::hex, std::ios::basefield);
306 i.unsetf(std::ios::showbase);
307 int hex;
308 i >> hex;
309 if ( i.fail() ){
310 throw LDAPUrlException(LDAPUrlException::URL_DECODING_ERROR,
311 "Invalid percent encoding");
312 }
313 char j = hex;
314 out.push_back(j);
315 startpos = pos+3;
316 pos = src.find('%', startpos);
317 }
318 out += src.substr(startpos, pos - startpos);
319 }
320
string2list(const std::string & src,StringList & sl,bool percentDecode)321 void LDAPUrl::string2list(const std::string &src, StringList& sl,
322 bool percentDecode)
323 {
324 std::string::size_type comma_startpos = 0;
325 std::string::size_type comma_pos = 0;
326 std::string actItem;
327 while ( comma_pos != std::string::npos ) {
328 comma_pos = src.find(',', comma_startpos);
329 actItem = src.substr(comma_startpos, comma_pos - comma_startpos);
330 if (percentDecode){
331 std::string decoded;
332 this->percentDecode(actItem,decoded);
333 actItem = decoded;
334 }
335 sl.add(actItem);
336 comma_startpos = comma_pos + 1;
337 }
338 }
339
340
components2Url() const341 void LDAPUrl::components2Url() const
342 {
343 std::ostringstream url;
344 std::string encoded = "";
345
346 url << m_Scheme << "://";
347 // IPv6 ?
348 if ( m_Host.find( ':', 0 ) != std::string::npos ) {
349 url << "[" << this->percentEncode(m_Host, encoded) << "]";
350 } else {
351 url << this->percentEncode(m_Host, encoded, PCT_ENCFLAG_SLASH);
352 }
353
354 if ( m_Port != 0 ) {
355 url << ":" << m_Port;
356 }
357
358 url << "/";
359 encoded = "";
360 if ( m_DN != "" ) {
361 this->percentEncode( m_DN, encoded );
362 url << encoded;
363 }
364 string qm = "";
365 if ( ! m_Attrs.empty() ){
366 url << "?";
367 bool first = true;
368 for ( StringList::const_iterator i = m_Attrs.begin();
369 i != m_Attrs.end(); i++)
370 {
371 this->percentEncode( *i, encoded );
372 if ( ! first ) {
373 url << ",";
374 } else {
375 first = false;
376 }
377 url << encoded;
378 }
379 } else {
380 qm.append("?");
381 }
382 if ( m_Scope == 1 ) {
383 url << qm << "?one";
384 qm = "";
385 } else if ( m_Scope == 2 ) {
386 url << qm << "?sub";
387 qm = "";
388 } else {
389 qm.append("?");
390 }
391 if (m_Filter != "" ){
392 this->percentEncode( m_Filter, encoded );
393 url << qm << "?" << encoded;
394 qm = "";
395 } else {
396 qm.append("?");
397 }
398
399 if ( ! m_Extensions.empty() ){
400 url << qm << "?";
401 bool first = true;
402 for ( StringList::const_iterator i = m_Extensions.begin();
403 i != m_Extensions.end(); i++)
404 {
405 this->percentEncode( *i, encoded, 1);
406 if ( ! first ) {
407 url << ",";
408 } else {
409 first = false;
410 }
411 url << encoded;
412 }
413 }
414 m_urlString=url.str();
415 }
416
417
percentEncode(const std::string & src,std::string & dest,int flags) const418 std::string& LDAPUrl::percentEncode( const std::string &src,
419 std::string &dest,
420 int flags) const
421 {
422 std::ostringstream o;
423 o.setf(std::ios::hex, std::ios::basefield);
424 o.setf(std::ios::uppercase);
425 o.unsetf(std::ios::showbase);
426 bool escape=false;
427 for ( std::string::const_iterator i = src.begin(); i != src.end(); i++ ){
428 switch(*i){
429 /* reserved */
430 case '?' :
431 escape = true;
432 break;
433 case ',' :
434 if ( flags & PCT_ENCFLAG_COMMA ) {
435 escape = true;
436 } else {
437 escape = false;
438 }
439 break;
440 case ':' :
441 case '/' :
442 if ( flags & PCT_ENCFLAG_SLASH ) {
443 escape = true;
444 } else {
445 escape = false;
446 }
447 break;
448 case '#' :
449 case '[' :
450 case ']' :
451 case '@' :
452 case '!' :
453 case '$' :
454 case '&' :
455 case '\'' :
456 case '(' :
457 case ')' :
458 case '*' :
459 case '+' :
460 case ';' :
461 case '=' :
462 /* unreserved */
463 case '-' :
464 case '.' :
465 case '_' :
466 case '~' :
467 escape = false;
468 break;
469 default :
470 if ( std::isalnum(*i) ) {
471 escape = false;
472 } else {
473 escape = true;
474 }
475 break;
476 }
477 if ( escape ) {
478 o << "%" << std::setw(2) << std::setfill('0') << (int)(unsigned char)*i ;
479 } else {
480 o.put(*i);
481 }
482 }
483 dest = o.str();
484 return dest;
485 }
486
487 const code2string_s LDAPUrlException::code2string[] = {
488 { INVALID_SCHEME, "Invalid URL Scheme" },
489 { INVALID_PORT, "Invalid Port in Url" },
490 { INVALID_SCOPE, "Invalid Search Scope in Url" },
491 { INVALID_URL, "Invalid LDAP Url" },
492 { URL_DECODING_ERROR, "Url-decoding Error" },
493 { 0, 0 }
494 };
495
LDAPUrlException(int code,const std::string & msg)496 LDAPUrlException::LDAPUrlException( int code, const std::string &msg) :
497 m_code(code), m_addMsg(msg) {}
498
getCode() const499 int LDAPUrlException::getCode() const
500 {
501 return m_code;
502 }
503
getAdditionalInfo() const504 const std::string LDAPUrlException::getAdditionalInfo() const
505 {
506 return m_addMsg;
507 }
508
getErrorMessage() const509 const std::string LDAPUrlException::getErrorMessage() const
510 {
511 for ( int i = 0; code2string[i].string != 0; i++ ) {
512 if ( code2string[i].code == m_code ) {
513 return std::string(code2string[i].string);
514 }
515 }
516 return "";
517
518 }
519