]> git.sur5r.net Git - openldap/blob - contrib/ldapc++/src/LdifReader.cpp
be more liberal when parsing LDIF Entries
[openldap] / contrib / ldapc++ / src / LdifReader.cpp
1 // $OpenLDAP$
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 }