Home | History | Annotate | Line # | Download | only in src
      1 // $OpenLDAP$
      2 /*
      3  * Copyright 2000-2024 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