1 | /* |
2 | A temporary copy to break dependency to KLDAP |
3 | |
4 | This file is part of libkldap. |
5 | SPDX-FileCopyrightText: 2004-2006 Szombathelyi György <gyurco@freemail.hu> |
6 | |
7 | SPDX-License-Identifier: LGPL-2.0-or-later |
8 | */ |
9 | |
10 | #include "ldif_p.h" |
11 | |
12 | #include "kcontacts_debug.h" |
13 | |
14 | class Q_DECL_HIDDEN Ldif::LdifPrivate |
15 | { |
16 | public: |
17 | int mModType; |
18 | bool mDelOldRdn, mUrl; |
19 | QByteArray mDn; |
20 | QString mAttr, mNewRdn, mNewSuperior, mOid; |
21 | QByteArray mLdif, mValue; |
22 | EntryType mEntryType; |
23 | |
24 | bool mIsNewLine, , mCritical; |
25 | ParseValue mLastParseValue; |
26 | uint mPos, mLineNumber; |
27 | QByteArray mLine; |
28 | }; |
29 | |
30 | Ldif::Ldif() |
31 | : d(new LdifPrivate) |
32 | { |
33 | startParsing(); |
34 | } |
35 | |
36 | Ldif::Ldif(const Ldif &that) |
37 | : d(new LdifPrivate) |
38 | { |
39 | *d = *that.d; |
40 | |
41 | startParsing(); |
42 | } |
43 | |
44 | Ldif &Ldif::operator=(const Ldif &that) |
45 | { |
46 | if (this == &that) { |
47 | return *this; |
48 | } |
49 | |
50 | *d = *that.d; |
51 | |
52 | return *this; |
53 | } |
54 | |
55 | Ldif::~Ldif() |
56 | { |
57 | delete d; |
58 | } |
59 | |
60 | QByteArray Ldif::assembleLine(const QString &fieldname, const QByteArray &value, uint linelen, bool url) |
61 | { |
62 | QByteArray result; |
63 | |
64 | if (url) { |
65 | result = fieldname.toUtf8() + ":< " + value; |
66 | } else { |
67 | bool safe = false; |
68 | bool isDn = fieldname.toLower() == QLatin1String("dn" ); |
69 | // SAFE-INIT-CHAR |
70 | if (value.size() > 0 && value[0] > 0 && value[0] != '\n' // |
71 | && value[0] != '\r' && value[0] != ':' && value[0] != '<') { |
72 | safe = true; |
73 | } |
74 | |
75 | // SAFE-CHAR |
76 | if (safe) { |
77 | for (int i = 1; i < value.size(); ++i) { |
78 | // allow utf-8 in Distinguished Names |
79 | if ((isDn && value[i] == 0) // |
80 | || (!isDn && value[i] <= 0) // |
81 | || value[i] == '\r' || value[i] == '\n') { |
82 | safe = false; |
83 | break; |
84 | } |
85 | } |
86 | } |
87 | |
88 | if (value.isEmpty()) { |
89 | safe = true; |
90 | } |
91 | |
92 | if (safe) { |
93 | result = fieldname.toUtf8() + ": " + value; |
94 | } else { |
95 | result = fieldname.toUtf8() + ":: " + value.toBase64(); |
96 | } |
97 | |
98 | if (linelen > 0) { |
99 | int i = (uint)(fieldname.length() + 2) > linelen ? fieldname.length() + 2 : linelen; |
100 | while (i < result.length()) { |
101 | result.insert(i, s: "\n " ); |
102 | i += linelen + 2; |
103 | } |
104 | } |
105 | } |
106 | return result; |
107 | } |
108 | |
109 | QByteArray Ldif::assembleLine(const QString &fieldname, const QString &value, uint linelen, bool url) |
110 | { |
111 | return assembleLine(fieldname, value: value.toUtf8(), linelen, url); |
112 | } |
113 | |
114 | bool Ldif::splitLine(const QByteArray &line, QString &fieldname, QByteArray &value) |
115 | { |
116 | int position; |
117 | int linelen; |
118 | |
119 | // qCDebug(KCONTACTS_LOG) << "line:" << QString::fromUtf8(line); |
120 | |
121 | position = line.indexOf(bv: ":" ); |
122 | if (position == -1) { |
123 | // strange: we did not find a fieldname |
124 | fieldname = QLatin1String("" ); |
125 | value = line.trimmed(); |
126 | // qCDebug(KCONTACTS_LOG) << "value :" << value[0]; |
127 | return false; |
128 | } |
129 | |
130 | linelen = line.size(); |
131 | fieldname = QString::fromUtf8(ba: line.left(len: position).trimmed()); |
132 | |
133 | if (linelen > (position + 1) && line[position + 1] == ':') { |
134 | // String is BASE64 encoded -> decode it now. |
135 | if (linelen <= (position + 3)) { |
136 | value.resize(size: 0); |
137 | return false; |
138 | } |
139 | value = QByteArray::fromBase64(base64: line.mid(index: position + 3)); |
140 | return false; |
141 | } |
142 | |
143 | if (linelen > (position + 1) && line[position + 1] == '<') { |
144 | // String is an URL. |
145 | if (linelen <= (position + 3)) { |
146 | value.resize(size: 0); |
147 | return false; |
148 | } |
149 | value = QByteArray::fromBase64(base64: line.mid(index: position + 3)); |
150 | return true; |
151 | } |
152 | |
153 | if (linelen <= (position + 2)) { |
154 | value.resize(size: 0); |
155 | return false; |
156 | } |
157 | value = line.mid(index: position + 2); |
158 | return false; |
159 | } |
160 | |
161 | bool Ldif::splitControl(const QByteArray &line, QString &oid, bool &critical, QByteArray &value) |
162 | { |
163 | QString tmp; |
164 | critical = false; |
165 | bool url = splitLine(line, fieldname&: tmp, value); |
166 | |
167 | qCDebug(KCONTACTS_LOG) << "value:" << QString::fromUtf8(ba: value); |
168 | if (tmp.isEmpty()) { |
169 | tmp = QString::fromUtf8(ba: value); |
170 | value.resize(size: 0); |
171 | } |
172 | if (tmp.endsWith(s: QLatin1String("true" ))) { |
173 | critical = true; |
174 | tmp.chop(n: 5); |
175 | } else if (tmp.endsWith(s: QLatin1String("false" ))) { |
176 | critical = false; |
177 | tmp.chop(n: 6); |
178 | } |
179 | oid = tmp; |
180 | return url; |
181 | } |
182 | |
183 | Ldif::ParseValue Ldif::processLine() |
184 | { |
185 | if (d->mIsComment) { |
186 | return None; |
187 | } |
188 | |
189 | ParseValue retval = None; |
190 | if (d->mLastParseValue == EndEntry) { |
191 | d->mEntryType = Entry_None; |
192 | } |
193 | |
194 | d->mUrl = splitLine(line: d->mLine, fieldname&: d->mAttr, value&: d->mValue); |
195 | |
196 | QString attrLower = d->mAttr.toLower(); |
197 | |
198 | switch (d->mEntryType) { |
199 | case Entry_None: |
200 | if (attrLower == QLatin1String("version" )) { |
201 | if (!d->mDn.isEmpty()) { |
202 | retval = Err; |
203 | } |
204 | } else if (attrLower == QLatin1String("dn" )) { |
205 | qCDebug(KCONTACTS_LOG) << "ldapentry dn:" << QString::fromUtf8(ba: d->mValue); |
206 | d->mDn = d->mValue; |
207 | d->mModType = Mod_None; |
208 | retval = NewEntry; |
209 | } else if (attrLower == QLatin1String("changetype" )) { |
210 | if (d->mDn.isEmpty()) { |
211 | retval = Err; |
212 | } else { |
213 | QString tmpval = QString::fromUtf8(ba: d->mValue); |
214 | qCDebug(KCONTACTS_LOG) << "changetype:" << tmpval; |
215 | if (tmpval == QLatin1String("add" )) { |
216 | d->mEntryType = Entry_Add; |
217 | } else if (tmpval == QLatin1String("delete" )) { |
218 | d->mEntryType = Entry_Del; |
219 | } else if (tmpval == QLatin1String("modrdn" ) || tmpval == QLatin1String("moddn" )) { |
220 | d->mNewRdn = QLatin1String("" ); |
221 | d->mNewSuperior = QLatin1String("" ); |
222 | d->mDelOldRdn = true; |
223 | d->mEntryType = Entry_Modrdn; |
224 | } else if (tmpval == QLatin1String("modify" )) { |
225 | d->mEntryType = Entry_Mod; |
226 | } else { |
227 | retval = Err; |
228 | } |
229 | } |
230 | } else if (attrLower == QLatin1String("control" )) { |
231 | d->mUrl = splitControl(line: d->mValue, oid&: d->mOid, critical&: d->mCritical, value&: d->mValue); |
232 | retval = Control; |
233 | } else if (!d->mAttr.isEmpty() && !d->mValue.isEmpty()) { |
234 | d->mEntryType = Entry_Add; |
235 | retval = Item; |
236 | } |
237 | break; |
238 | case Entry_Add: |
239 | if (d->mAttr.isEmpty() && d->mValue.isEmpty()) { |
240 | retval = EndEntry; |
241 | } else { |
242 | retval = Item; |
243 | } |
244 | break; |
245 | case Entry_Del: |
246 | if (d->mAttr.isEmpty() && d->mValue.isEmpty()) { |
247 | retval = EndEntry; |
248 | } else { |
249 | retval = Err; |
250 | } |
251 | break; |
252 | case Entry_Mod: |
253 | if (d->mModType == Mod_None) { |
254 | qCDebug(KCONTACTS_LOG) << "new modtype" << d->mAttr; |
255 | if (d->mAttr.isEmpty() && d->mValue.isEmpty()) { |
256 | retval = EndEntry; |
257 | } else if (attrLower == QLatin1String("add" )) { |
258 | d->mModType = Mod_Add; |
259 | } else if (attrLower == QLatin1String("replace" )) { |
260 | d->mModType = Mod_Replace; |
261 | d->mAttr = QString::fromUtf8(ba: d->mValue); |
262 | d->mValue = QByteArray(); |
263 | retval = Item; |
264 | } else if (attrLower == QLatin1String("delete" )) { |
265 | d->mModType = Mod_Del; |
266 | d->mAttr = QString::fromUtf8(ba: d->mValue); |
267 | d->mValue = QByteArray(); |
268 | retval = Item; |
269 | } else { |
270 | retval = Err; |
271 | } |
272 | } else { |
273 | if (d->mAttr.isEmpty()) { |
274 | if (QString::fromUtf8(ba: d->mValue) == QLatin1String("-" )) { |
275 | d->mModType = Mod_None; |
276 | } else if (d->mValue.isEmpty()) { |
277 | retval = EndEntry; |
278 | } else { |
279 | retval = Err; |
280 | } |
281 | } else { |
282 | retval = Item; |
283 | } |
284 | } |
285 | break; |
286 | case Entry_Modrdn: |
287 | if (d->mAttr.isEmpty() && d->mValue.isEmpty()) { |
288 | retval = EndEntry; |
289 | } else if (attrLower == QLatin1String("newrdn" )) { |
290 | d->mNewRdn = QString::fromUtf8(ba: d->mValue); |
291 | } else if (attrLower == QLatin1String("newsuperior" )) { |
292 | d->mNewSuperior = QString::fromUtf8(ba: d->mValue); |
293 | } else if (attrLower == QLatin1String("deleteoldrdn" )) { |
294 | if (d->mValue.size() > 0 && d->mValue[0] == '0') { |
295 | d->mDelOldRdn = false; |
296 | } else if (d->mValue.size() > 0 && d->mValue[0] == '1') { |
297 | d->mDelOldRdn = true; |
298 | } else { |
299 | retval = Err; |
300 | } |
301 | } else { |
302 | retval = Err; |
303 | } |
304 | break; |
305 | } |
306 | return retval; |
307 | } |
308 | |
309 | Ldif::ParseValue Ldif::nextItem() |
310 | { |
311 | ParseValue retval = None; |
312 | char c = 0; |
313 | |
314 | while (retval == None) { |
315 | if (d->mPos < (uint)d->mLdif.size()) { |
316 | c = d->mLdif[d->mPos]; |
317 | d->mPos++; |
318 | if (d->mIsNewLine && c == '\r') { |
319 | continue; // handle \n\r line end |
320 | } |
321 | if (d->mIsNewLine && (c == ' ' || c == '\t')) { // line folding |
322 | d->mIsNewLine = false; |
323 | continue; |
324 | } |
325 | if (d->mIsNewLine) { |
326 | d->mIsNewLine = false; |
327 | retval = processLine(); |
328 | d->mLastParseValue = retval; |
329 | d->mLine.resize(size: 0); |
330 | d->mIsComment = (c == '#'); |
331 | } |
332 | if (c == '\n' || c == '\r') { |
333 | d->mLineNumber++; |
334 | d->mIsNewLine = true; |
335 | continue; |
336 | } |
337 | } else { |
338 | retval = MoreData; |
339 | break; |
340 | } |
341 | |
342 | if (!d->mIsComment) { |
343 | d->mLine += c; |
344 | } |
345 | } |
346 | return retval; |
347 | } |
348 | |
349 | void Ldif::endLdif() |
350 | { |
351 | QByteArray tmp(3, '\n'); |
352 | d->mLdif = tmp; |
353 | d->mPos = 0; |
354 | } |
355 | |
356 | void Ldif::startParsing() |
357 | { |
358 | d->mPos = d->mLineNumber = 0; |
359 | d->mDelOldRdn = false; |
360 | d->mEntryType = Entry_None; |
361 | d->mModType = Mod_None; |
362 | d->mNewRdn.clear(); |
363 | d->mNewSuperior.clear(); |
364 | d->mLine = QByteArray(); |
365 | d->mIsNewLine = false; |
366 | d->mIsComment = false; |
367 | d->mLastParseValue = None; |
368 | } |
369 | |
370 | void Ldif::setLdif(const QByteArray &ldif) |
371 | { |
372 | d->mLdif = ldif; |
373 | d->mPos = 0; |
374 | } |
375 | |
376 | Ldif::EntryType Ldif::entryType() const |
377 | { |
378 | return d->mEntryType; |
379 | } |
380 | |
381 | int Ldif::modType() const |
382 | { |
383 | return d->mModType; |
384 | } |
385 | |
386 | QString Ldif::newRdn() const |
387 | { |
388 | return d->mNewRdn; |
389 | } |
390 | |
391 | QString Ldif::newSuperior() const |
392 | { |
393 | return d->mNewSuperior; |
394 | } |
395 | |
396 | bool Ldif::delOldRdn() const |
397 | { |
398 | return d->mDelOldRdn; |
399 | } |
400 | |
401 | QString Ldif::attr() const |
402 | { |
403 | return d->mAttr; |
404 | } |
405 | |
406 | QByteArray Ldif::value() const |
407 | { |
408 | return d->mValue; |
409 | } |
410 | |
411 | bool Ldif::isUrl() const |
412 | { |
413 | return d->mUrl; |
414 | } |
415 | |
416 | bool Ldif::isCritical() const |
417 | { |
418 | return d->mCritical; |
419 | } |
420 | |
421 | QString Ldif::oid() const |
422 | { |
423 | return d->mOid; |
424 | } |
425 | |
426 | uint Ldif::lineNumber() const |
427 | { |
428 | return d->mLineNumber; |
429 | } |
430 | |