1 // $OpenLDAP: pkg/ldap/contrib/ldapc++/src/LdifReader.cpp,v 1.4.2.4 2008/07/09 21:45:42 quanah Exp $ 2 /* 3 * Copyright 2008, OpenLDAP Foundation, All Rights Reserved. 4 * COPYING RESTRICTIONS APPLY, see COPYRIGHT file 5 */ 6 7 #include "LdifReader.h" 8 #include "LDAPMessage.h" 9 #include "LDAPEntry.h" 10 #include "LDAPAttributeList.h" 11 #include "LDAPAttribute.h" 12 #include "LDAPUrl.h" 13 #include "debug.h" 14 15 #include <string> 16 #include <sstream> 17 #include <stdexcept> 18 19 #include <sasl/saslutil.h> // For base64 routines 20 21 typedef std::pair<std::string, std::string> stringpair; 22 23 LdifReader::LdifReader( std::istream &input ) 24 : m_ldifstream(input), m_lineNumber(0) 25 { 26 DEBUG(LDAP_DEBUG_TRACE, "<> LdifReader::LdifReader()" << std::endl); 27 this->m_version = 0; 28 // read the first record to find out version and type of the LDIF 29 this->readNextRecord(true); 30 this->m_currentIsFirst = true; 31 } 32 33 int LdifReader::readNextRecord( bool first ) 34 { 35 DEBUG(LDAP_DEBUG_TRACE, "-> LdifReader::readRecord()" << std::endl); 36 std::string line; 37 std::string type; 38 std::string value; 39 int numLine = 0; 40 int recordType = 0; 41 42 if ( (! first) && this->m_currentIsFirst == true ) 43 { 44 this->m_currentIsFirst = false; 45 return m_curRecType; 46 } 47 48 m_currentRecord.clear(); 49 50 while ( !this->getLdifLine(line) ) 51 { 52 DEBUG(LDAP_DEBUG_TRACE, " Line: " << line << std::endl ); 53 54 // skip comments and empty lines between entries 55 if ( line[0] == '#' || ( numLine == 0 && line.size() == 0 ) ) 56 { 57 DEBUG(LDAP_DEBUG_TRACE, "skipping empty line or comment" << std::endl ); 58 continue; 59 } 60 if ( line.size() == 0 ) 61 { 62 // End of Entry 63 break; 64 } 65 66 this->splitLine(line, type, value); 67 68 if ( numLine == 0 ) 69 { 70 if ( type == "version" ) 71 { 72 std::istringstream valuestream(value); 73 valuestream >> this->m_version; 74 if ( this->m_version != 1 ) // there is no other Version than LDIFv1 75 { 76 std::ostringstream err; 77 err << "Line " << this->m_lineNumber 78 << ": Unsuported LDIF Version"; 79 throw( std::runtime_error(err.str()) ); 80 } 81 continue; 82 } 83 if ( type == "dn" ) // Record should start with the DN ... 84 { 85 DEBUG(LDAP_DEBUG_TRACE, " Record DN:" << value << std::endl); 86 } 87 else if ( type == "include" ) // ... or it might be an "include" line 88 { 89 DEBUG(LDAP_DEBUG_TRACE, " Include directive: " << value << std::endl); 90 if ( this->m_version == 1 ) 91 { 92 std::ostringstream err; 93 err << "Line " << this->m_lineNumber 94 << ": \"include\" not allowed in LDIF version 1."; 95 throw( std::runtime_error(err.str()) ); 96 } 97 else 98 { 99 std::ostringstream err; 100 err << "Line " << this->m_lineNumber 101 << ": \"include\" not yet suppported."; 102 throw( std::runtime_error(err.str()) ); 103 } 104 } 105 else 106 { 107 DEBUG(LDAP_DEBUG_TRACE, " Record doesn't start with a DN" 108 << std::endl); 109 std::ostringstream err; 110 err << "Line " << this->m_lineNumber 111 << ": LDIF record does not start with a DN."; 112 throw( std::runtime_error(err.str()) ); 113 } 114 } 115 if ( numLine == 1 ) // might contain "changtype" to indicate a change request 116 { 117 if ( type == "changetype" ) 118 { 119 if ( first ) 120 { 121 this->m_ldifTypeRequest = true; 122 } 123 else if (! this->m_ldifTypeRequest ) 124 { 125 // Change Request in Entry record LDIF, should we accept it? 126 std::ostringstream err; 127 err << "Line " << this->m_lineNumber 128 << ": Change Request in an entry-only LDIF."; 129 throw( std::runtime_error(err.str()) ); 130 } 131 if ( value == "modify" ) 132 { 133 recordType = LDAPMsg::MODIFY_REQUEST; 134 } 135 else if ( value == "add" ) 136 { 137 recordType = LDAPMsg::ADD_REQUEST; 138 } 139 else if ( value == "delete" ) 140 { 141 recordType = LDAPMsg::DELETE_REQUEST; 142 } 143 else if ( value == "modrdn" ) 144 { 145 recordType = LDAPMsg::MODRDN_REQUEST; 146 } 147 else 148 { 149 DEBUG(LDAP_DEBUG_TRACE, " Unknown change request <" 150 << value << ">" << std::endl); 151 std::ostringstream err; 152 err << "Line " << this->m_lineNumber 153 << ": Unknown changetype: \"" << value << "\"."; 154 throw( std::runtime_error(err.str()) ); 155 } 156 } 157 else 158 { 159 if ( first ) 160 { 161 this->m_ldifTypeRequest = false; 162 } 163 else if (this->m_ldifTypeRequest ) 164 { 165 // Entry record in Change record LDIF, should we accept 166 // it (e.g. as AddRequest)? 167 } 168 recordType = LDAPMsg::SEARCH_ENTRY; 169 } 170 } 171 m_currentRecord.push_back( stringpair(type, value) ); 172 numLine++; 173 } 174 DEBUG(LDAP_DEBUG_TRACE, "<- LdifReader::readRecord() return: " 175 << recordType << std::endl); 176 m_curRecType = recordType; 177 return recordType; 178 } 179 180 LDAPEntry LdifReader::getEntryRecord() 181 { 182 if ( m_curRecType != LDAPMsg::SEARCH_ENTRY ) 183 { 184 // Error 185 } 186 std::list<stringpair>::const_iterator i = m_currentRecord.begin(); 187 LDAPEntry resEntry(i->second); 188 i++; 189 LDAPAttribute curAttr(i->first); 190 LDAPAttributeList *curAl = new LDAPAttributeList(); 191 for ( ; i != m_currentRecord.end(); i++ ) 192 { 193 if ( i->first == curAttr.getName() ) 194 { 195 curAttr.addValue(i->second); 196 } 197 else 198 { 199 const LDAPAttribute* existing = curAl->getAttributeByName( i->first ); 200 if ( existing ) 201 { 202 // Attribute exists already (handle gracefully) 203 curAl->addAttribute( curAttr ); 204 curAttr = LDAPAttribute( *existing ); 205 curAttr.addValue(i->second); 206 curAl->delAttribute( i->first ); 207 } 208 else 209 { 210 curAl->addAttribute( curAttr ); 211 curAttr = LDAPAttribute( i->first, i->second ); 212 } 213 } 214 } 215 curAl->addAttribute( curAttr ); 216 resEntry.setAttributes( curAl ); 217 return resEntry; 218 } 219 220 int LdifReader::getLdifLine(std::string &ldifline) 221 { 222 DEBUG(LDAP_DEBUG_TRACE, "-> LdifReader::getLdifLine()" << std::endl); 223 224 this->m_lineNumber++; 225 if ( ! getline(m_ldifstream, ldifline) ) 226 { 227 return -1; 228 } 229 while ( m_ldifstream && 230 (m_ldifstream.peek() == ' ' || m_ldifstream.peek() == '\t')) 231 { 232 std::string cat; 233 m_ldifstream.ignore(); 234 getline(m_ldifstream, cat); 235 ldifline += cat; 236 this->m_lineNumber++; 237 } 238 239 DEBUG(LDAP_DEBUG_TRACE, "<- LdifReader::getLdifLine()" << std::endl); 240 return 0; 241 } 242 243 void LdifReader::splitLine( 244 const std::string& line, 245 std::string &type, 246 std::string &value) const 247 { 248 std::string::size_type pos = line.find(':'); 249 if ( pos == std::string::npos ) 250 { 251 DEBUG(LDAP_DEBUG_ANY, "Invalid LDIF line. No `:` separator" 252 << std::endl ); 253 std::ostringstream err; 254 err << "Line " << this->m_lineNumber << ": Invalid LDIF line. No `:` separator"; 255 throw( std::runtime_error( err.str() )); 256 } 257 258 type = line.substr(0, pos); 259 if ( pos == line.size() ) 260 { 261 // empty value 262 value = ""; 263 return; 264 } 265 266 pos++; 267 char delim = line[pos]; 268 if ( delim == ':' || delim == '<' ) 269 { 270 pos++; 271 } 272 273 for( ; pos < line.size() && isspace(line[pos]); pos++ ) 274 { /* empty */ } 275 276 value = line.substr(pos); 277 278 if ( delim == ':' ) 279 { 280 // Base64 encoded value 281 DEBUG(LDAP_DEBUG_TRACE, " base64 encoded value" << std::endl ); 282 char outbuf[value.size()]; 283 int rc = sasl_decode64(value.c_str(), value.size(), 284 outbuf, value.size(), NULL); 285 if( rc == SASL_OK ) 286 { 287 value = std::string(outbuf); 288 } 289 else if ( rc == SASL_BADPROT ) 290 { 291 value = ""; 292 DEBUG( LDAP_DEBUG_TRACE, " invalid base64 content" << std::endl ); 293 std::ostringstream err; 294 err << "Line " << this->m_lineNumber << ": Can't decode Base64 data"; 295 throw( std::runtime_error( err.str() )); 296 } 297 else if ( rc == SASL_BUFOVER ) 298 { 299 value = ""; 300 DEBUG( LDAP_DEBUG_TRACE, " not enough space in output buffer" 301 << std::endl ); 302 std::ostringstream err; 303 err << "Line " << this->m_lineNumber 304 << ": Can't decode Base64 data. Buffer too small"; 305 throw( std::runtime_error( err.str() )); 306 } 307 } 308 else if ( delim == '<' ) 309 { 310 // URL value 311 DEBUG(LDAP_DEBUG_TRACE, " url value" << std::endl ); 312 std::ostringstream err; 313 err << "Line " << this->m_lineNumber 314 << ": URLs are currently not supported"; 315 throw( std::runtime_error( err.str() )); 316 } 317 else 318 { 319 // "normal" value 320 DEBUG(LDAP_DEBUG_TRACE, " string value" << std::endl ); 321 } 322 DEBUG(LDAP_DEBUG_TRACE, " Type: <" << type << ">" << std::endl ); 323 DEBUG(LDAP_DEBUG_TRACE, " Value: <" << value << ">" << std::endl ); 324 return; 325 } 326 327 std::string LdifReader::readIncludeLine( const std::string& line ) const 328 { 329 std::string::size_type pos = sizeof("file:") - 1; 330 std::string scheme = line.substr( 0, pos ); 331 std::string file; 332 333 // only file:// URLs supported currently 334 if ( scheme != "file:" ) 335 { 336 DEBUG( LDAP_DEBUG_TRACE, "unsupported scheme: " << scheme 337 << std::endl); 338 } 339 else if ( line[pos] == '/' ) 340 { 341 if ( line[pos+1] == '/' ) 342 { 343 pos += 2; 344 } 345 file = line.substr(pos, std::string::npos); 346 DEBUG( LDAP_DEBUG_TRACE, "target file: " << file << std::endl); 347 } 348 return file; 349 } 350