1 // $OpenLDAP$ 2 /* 3 * Copyright 2000-2014 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 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 38 LDAPUrl::~LDAPUrl() 39 { 40 DEBUG(LDAP_DEBUG_DESTROY, "LDAPUrl::~LDAPUrl()" << endl); 41 m_Attrs.clear(); 42 } 43 44 int LDAPUrl::getPort() const 45 { 46 return m_Port; 47 } 48 49 void LDAPUrl::setPort(int port) 50 { 51 m_Port = port; 52 regenerate = true; 53 } 54 55 int LDAPUrl::getScope() const 56 { 57 return m_Scope; 58 } 59 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 75 const string& LDAPUrl::getURLString() const 76 { 77 if (regenerate){ 78 this->components2Url(); 79 regenerate=false; 80 } 81 return m_urlString; 82 } 83 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 93 const string& LDAPUrl::getHost() const 94 { 95 return m_Host; 96 } 97 98 void LDAPUrl::setHost( const std::string &host ) 99 { 100 m_Host = host; 101 regenerate = true; 102 } 103 104 const string& LDAPUrl::getDN() const 105 { 106 return m_DN; 107 } 108 void LDAPUrl::setDN( const std::string &dn ) 109 { 110 m_DN = dn; 111 regenerate = true; 112 } 113 114 const string& LDAPUrl::getFilter() const 115 { 116 return m_Filter; 117 } 118 void LDAPUrl::setFilter( const std::string &filter ) 119 { 120 m_Filter = filter; 121 regenerate = true; 122 } 123 124 const StringList& LDAPUrl::getAttrs() const 125 { 126 return m_Attrs; 127 } 128 void LDAPUrl::setAttrs( const StringList &attrs ) 129 { 130 m_Attrs = attrs; 131 regenerate = true; 132 } 133 134 const StringList& LDAPUrl::getExtensions() const 135 { 136 return m_Extensions; 137 } 138 139 void LDAPUrl::setExtensions( const StringList &ext ) 140 { 141 m_Extensions = ext; 142 regenerate = true; 143 } 144 145 const std::string& LDAPUrl::getScheme() const 146 { 147 return m_Scheme; 148 } 149 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 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 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 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 341 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 418 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 496 LDAPUrlException::LDAPUrlException( int code, const std::string &msg) : 497 m_code(code), m_addMsg(msg) {} 498 499 int LDAPUrlException::getCode() const 500 { 501 return m_code; 502 } 503 504 const std::string LDAPUrlException::getAdditionalInfo() const 505 { 506 return m_addMsg; 507 } 508 509 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