1// OpenLDAP: pkg/ldap/contrib/ldapc++/src/LdifReader.cpp,v 1.4.2.5 2009/09/29 21:35:03 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
21typedef std::pair<std::string, std::string> stringpair;
22
23LdifReader::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
33int 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
180LDAPEntry LdifReader::getEntryRecord()
181{
182    std::list<stringpair>::const_iterator i = m_currentRecord.begin();
183    if ( m_curRecType != LDAPMsg::SEARCH_ENTRY )
184    {
185        throw( std::runtime_error( "The LDIF record: '" + i->second +
186                                   "' is not a valid LDAP Entry" ));
187    }
188    LDAPEntry resEntry(i->second);
189    i++;
190    LDAPAttribute curAttr(i->first);
191    LDAPAttributeList *curAl = new LDAPAttributeList();
192    for ( ; i != m_currentRecord.end(); i++ )
193    {
194        if ( i->first == curAttr.getName() )
195        {
196            curAttr.addValue(i->second);
197        }
198        else
199        {
200            const LDAPAttribute* existing = curAl->getAttributeByName( i->first );
201            if ( existing )
202            {
203                // Attribute exists already (handle gracefully)
204                curAl->addAttribute( curAttr );
205                curAttr = LDAPAttribute( *existing );
206                curAttr.addValue(i->second);
207                curAl->delAttribute( i->first );
208            }
209            else
210            {
211                curAl->addAttribute( curAttr );
212                curAttr = LDAPAttribute( i->first, i->second );
213            }
214        }
215    }
216    curAl->addAttribute( curAttr );
217    resEntry.setAttributes( curAl );
218    return resEntry;
219}
220
221int LdifReader::getLdifLine(std::string &ldifline)
222{
223    DEBUG(LDAP_DEBUG_TRACE, "-> LdifReader::getLdifLine()" << std::endl);
224
225    this->m_lineNumber++;
226    if ( ! getline(m_ldifstream, ldifline) )
227    {
228        return -1;
229    }
230    while ( m_ldifstream &&
231        (m_ldifstream.peek() == ' ' || m_ldifstream.peek() == '\t'))
232    {
233        std::string cat;
234        m_ldifstream.ignore();
235        getline(m_ldifstream, cat);
236        ldifline += cat;
237        this->m_lineNumber++;
238    }
239
240    DEBUG(LDAP_DEBUG_TRACE, "<- LdifReader::getLdifLine()" << std::endl);
241    return 0;
242}
243
244void LdifReader::splitLine(
245            const std::string& line,
246            std::string &type,
247            std::string &value) const
248{
249    std::string::size_type pos = line.find(':');
250    if ( pos == std::string::npos )
251    {
252        DEBUG(LDAP_DEBUG_ANY, "Invalid LDIF line. No `:` separator"
253                << std::endl );
254        std::ostringstream err;
255        err << "Line " << this->m_lineNumber << ": Invalid LDIF line. No `:` separator";
256        throw( std::runtime_error( err.str() ));
257    }
258
259    type = line.substr(0, pos);
260    if ( pos == line.size() )
261    {
262        // empty value
263        value = "";
264        return;
265    }
266
267    pos++;
268    char delim = line[pos];
269    if ( delim == ':' || delim == '<' )
270    {
271        pos++;
272    }
273
274    for( ; pos < line.size() && isspace(line[pos]); pos++ )
275    { /* empty */ }
276
277    value = line.substr(pos);
278
279    if ( delim == ':' )
280    {
281        // Base64 encoded value
282        DEBUG(LDAP_DEBUG_TRACE, "  base64 encoded value" << std::endl );
283        char outbuf[value.size()];
284        int rc = sasl_decode64(value.c_str(), value.size(),
285                outbuf, value.size(), NULL);
286        if( rc == SASL_OK )
287        {
288            value = std::string(outbuf);
289        }
290        else if ( rc == SASL_BADPROT )
291        {
292            value = "";
293            DEBUG( LDAP_DEBUG_TRACE, " invalid base64 content" << std::endl );
294            std::ostringstream err;
295            err << "Line " << this->m_lineNumber << ": Can't decode Base64 data";
296            throw( std::runtime_error( err.str() ));
297        }
298        else if ( rc == SASL_BUFOVER )
299        {
300            value = "";
301            DEBUG( LDAP_DEBUG_TRACE, " not enough space in output buffer"
302                    << std::endl );
303            std::ostringstream err;
304            err << "Line " << this->m_lineNumber
305                << ": Can't decode Base64 data. Buffer too small";
306            throw( std::runtime_error( err.str() ));
307        }
308    }
309    else if ( delim == '<' )
310    {
311        // URL value
312        DEBUG(LDAP_DEBUG_TRACE, "  url value" << std::endl );
313        std::ostringstream err;
314        err << "Line " << this->m_lineNumber
315            << ": URLs are currently not supported";
316        throw( std::runtime_error( err.str() ));
317    }
318    else
319    {
320        // "normal" value
321        DEBUG(LDAP_DEBUG_TRACE, "  string value" << std::endl );
322    }
323    DEBUG(LDAP_DEBUG_TRACE, "  Type: <" << type << ">" << std::endl );
324    DEBUG(LDAP_DEBUG_TRACE, "  Value: <" << value << ">" << std::endl );
325    return;
326}
327
328std::string LdifReader::readIncludeLine( const std::string& line ) const
329{
330    std::string::size_type pos = sizeof("file:") - 1;
331    std::string scheme = line.substr( 0, pos );
332    std::string file;
333
334    // only file:// URLs supported currently
335    if ( scheme != "file:" )
336    {
337        DEBUG( LDAP_DEBUG_TRACE, "unsupported scheme: " << scheme
338                << std::endl);
339    }
340    else if ( line[pos] == '/' )
341    {
342        if ( line[pos+1] == '/' )
343        {
344            pos += 2;
345        }
346        file = line.substr(pos, std::string::npos);
347        DEBUG( LDAP_DEBUG_TRACE, "target file: " << file << std::endl);
348    }
349    return file;
350}
351