Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : */
8 :
9 : #include <errno.h>
10 : #include <string.h>
11 : #include <unistd.h>
12 :
13 : #include <libxml/xmlreader.h>
14 : #include <libxml/xmlschemas.h>
15 :
16 : #include "kdbtools.h"
17 : #include <kdbinternal.h>
18 :
19 : /*
20 : * Processes the current <key> node from reader, converting from XML
21 : * to a Key object, and ksAppendKey() it to ks.
22 : *
23 : * See keyToStream() for an example of a <key> node.
24 : *
25 : * This function is completely dependent on libxml.
26 : *
27 : * @param ks where to put the resulting read key
28 : * @param context a parent key name, so a full name can be calculated
29 : * if the XML node for the current key only provides a basename
30 : * @param reader where to read from
31 : */
32 110 : static int consumeKeyNode (KeySet * ks, const char * context, xmlTextReaderPtr reader)
33 : {
34 : /* printf("%s", KDB_SCHEMA_PATH); */
35 :
36 110 : xmlChar * keyNodeName = xmlTextReaderName (reader);
37 110 : if (!strcmp ((char *) keyNodeName, "key"))
38 : {
39 110 : xmlChar * privateContext = 0;
40 110 : int appended = 0;
41 110 : mode_t isdir = 0;
42 110 : int isbin = 0;
43 110 : int end = 0;
44 :
45 110 : Key * newKey = keyNew (0);
46 :
47 : /* a <key> must have one of the following:
48 : - a "name" attribute, used as an absolute name overriding the context
49 : - a "basename" attribute, that will be appended to the current context
50 : - a "parent" plus "basename" attributes, both appended to current context
51 : - only a "parent", appended to current context
52 : */
53 110 : xmlChar * buffer = xmlTextReaderGetAttribute (reader, (const xmlChar *) "name");
54 110 : if (buffer)
55 : {
56 : /* set absolute name */
57 0 : keySetName (newKey, (char *) buffer);
58 0 : xmlFree (buffer);
59 0 : buffer = 0;
60 : }
61 : else
62 : {
63 : /* logic for relative name calculation */
64 :
65 110 : privateContext = xmlTextReaderGetAttribute (reader, (const xmlChar *) "parent");
66 110 : buffer = xmlTextReaderGetAttribute (reader, (const xmlChar *) "basename");
67 :
68 110 : if (context) keySetName (newKey, context);
69 110 : if (privateContext) keyAddName (newKey, (char *) privateContext);
70 110 : if (buffer) keyAddName (newKey, (char *) buffer);
71 :
72 110 : xmlFree (privateContext);
73 110 : privateContext = 0;
74 110 : xmlFree (buffer);
75 110 : buffer = 0;
76 : }
77 :
78 :
79 : /* test for a short value attribute, instead of <value> below */
80 110 : buffer = xmlTextReaderGetAttribute (reader, (const xmlChar *) "value");
81 110 : if (buffer)
82 : {
83 76 : keySetRaw (newKey, buffer, elektraStrLen ((char *) buffer));
84 76 : xmlFree (buffer);
85 76 : buffer = 0;
86 : }
87 :
88 : /* Parse UID */
89 110 : buffer = xmlTextReaderGetAttribute (reader, (const xmlChar *) "uid");
90 110 : if (buffer)
91 : {
92 2 : int errsave = errno;
93 : char * endptr;
94 2 : long int uid = strtol ((const char *) buffer, &endptr, 10);
95 2 : errno = errsave;
96 2 : if (endptr && *endptr == '\0')
97 : {
98 2 : keySetUID (newKey, uid);
99 : }
100 2 : xmlFree (buffer);
101 2 : buffer = 0;
102 : }
103 :
104 : /* Parse GID */
105 110 : buffer = xmlTextReaderGetAttribute (reader, (const xmlChar *) "gid");
106 110 : if (buffer)
107 : {
108 2 : int errsave = errno;
109 : char * endptr;
110 2 : long int gid = strtol ((const char *) buffer, &endptr, 10);
111 2 : errno = errsave;
112 2 : if (endptr && *endptr == '\0')
113 : {
114 2 : keySetGID (newKey, gid);
115 : }
116 2 : xmlFree (buffer);
117 2 : buffer = 0;
118 : }
119 :
120 : /* Parse mode permissions */
121 110 : buffer = xmlTextReaderGetAttribute (reader, (const xmlChar *) "mode");
122 110 : int errsave = errno;
123 110 : if (buffer) keySetMode (newKey, strtol ((char *) buffer, 0, 0));
124 110 : errno = errsave;
125 110 : xmlFree (buffer);
126 :
127 :
128 110 : if (xmlTextReaderIsEmptyElement (reader))
129 : {
130 : /* we have a <key ..../> element */
131 0 : if (newKey && !appended)
132 : {
133 0 : ksAppendKey (ks, newKey);
134 0 : appended = 1;
135 0 : end = 1;
136 : }
137 : }
138 :
139 110 : buffer = xmlTextReaderGetAttribute (reader, (const xmlChar *) "type");
140 110 : if (buffer)
141 : {
142 110 : if (!strcmp ((char *) buffer, "binary"))
143 : isbin = 1;
144 105 : else if (!strcmp ((char *) buffer, "bin"))
145 1 : isbin = 1;
146 : }
147 110 : xmlFree (buffer);
148 :
149 : /* If "isdir" appears, everything different from "0", "false" or "no"
150 : marks it as a dir key */
151 110 : buffer = xmlTextReaderGetAttribute (reader, (const xmlChar *) "isdir");
152 110 : if (buffer)
153 : {
154 37 : if (strcmp ((char *) buffer, "0") && strcmp ((char *) buffer, "false") && strcmp ((char *) buffer, "no"))
155 : isdir = 1;
156 : else
157 1 : isdir = 0;
158 : }
159 110 : xmlFree (buffer);
160 :
161 110 : if (isdir) keySetDir (newKey);
162 110 : if (isbin) keySetMeta (newKey, "binary", "");
163 :
164 : // TODO: should parse arbitrary attributes as metadata
165 :
166 : /* Parse everything else */
167 592 : while (!end)
168 : {
169 482 : xmlTextReaderRead (reader);
170 482 : xmlChar * nodeName = xmlTextReaderName (reader);
171 :
172 482 : if (!strcmp ((char *) nodeName, "value"))
173 : {
174 2 : if (xmlTextReaderIsEmptyElement (reader) || xmlTextReaderNodeType (reader) == 15)
175 : {
176 1 : xmlFree (nodeName);
177 1 : continue;
178 : }
179 :
180 1 : xmlTextReaderRead (reader);
181 1 : buffer = xmlTextReaderValue (reader);
182 :
183 1 : if (buffer)
184 : {
185 : /* Key's value type was already set above */
186 1 : if (keyIsBinary (newKey))
187 : {
188 : /* TODO binary values
189 : char *unencoded=0;
190 : size_t unencodedSize;
191 :
192 : unencodedSize=elektraStrLen((char *)buffer)/2;
193 : unencoded=elektraMalloc(unencodedSize);
194 : unencodedSize=kdbbDecode((char *)buffer,unencoded);
195 : if (!unencodedSize) return -1;
196 : keySetRaw(newKey,unencoded,unencodedSize);
197 : elektraFree (unencoded);
198 : */
199 : }
200 : else
201 1 : keySetRaw (newKey, buffer, elektraStrLen ((char *) buffer));
202 : }
203 1 : xmlFree (buffer);
204 : }
205 480 : else if (!strcmp ((char *) nodeName, "comment"))
206 : {
207 162 : ssize_t commentSize = 0;
208 :
209 162 : if (xmlTextReaderIsEmptyElement (reader) || xmlTextReaderNodeType (reader) == 15)
210 : {
211 81 : xmlFree (nodeName);
212 81 : continue;
213 : }
214 :
215 81 : xmlTextReaderRead (reader);
216 81 : buffer = xmlTextReaderValue (reader);
217 :
218 81 : if ((commentSize = keyGetCommentSize (newKey)) > 1)
219 : {
220 : /*Multiple line comment*/
221 1 : char * tmpComment = 0;
222 1 : tmpComment = elektraMalloc (commentSize + xmlStrlen (buffer) * sizeof (xmlChar) + 1);
223 :
224 1 : if (tmpComment)
225 : {
226 1 : keyGetComment (newKey, tmpComment, commentSize);
227 :
228 1 : strcat (tmpComment, "\n");
229 1 : strcat (tmpComment, (char *) buffer);
230 :
231 1 : keySetComment (newKey, tmpComment);
232 :
233 1 : elektraFree (tmpComment);
234 1 : tmpComment = 0;
235 : }
236 : }
237 : else
238 80 : keySetComment (newKey, (char *) buffer);
239 81 : xmlFree (buffer);
240 : }
241 318 : else if (!strcmp ((char *) nodeName, "key"))
242 : {
243 : /* Here we found </key> or a sub <key>.
244 : So include current key in the KeySet. */
245 195 : if (newKey && !appended)
246 : {
247 110 : ksAppendKey (ks, newKey);
248 110 : appended = 1;
249 : }
250 :
251 195 : if (xmlTextReaderNodeType (reader) == 15) /* found a </key> */
252 : end = 1;
253 85 : else if (newKey)
254 : {
255 : /* found a sub <key> */
256 : /* prepare the context (parent) */
257 85 : consumeKeyNode (ks, newKey->key, reader);
258 : }
259 : }
260 :
261 400 : xmlFree (nodeName);
262 : }
263 :
264 : if (privateContext) xmlFree (privateContext);
265 :
266 : /* seems like we forgot the key, lets delete it */
267 110 : if (newKey && !appended)
268 : {
269 0 : keyDel (newKey);
270 : }
271 : }
272 :
273 110 : xmlFree (keyNodeName);
274 :
275 110 : return 0;
276 : }
277 :
278 :
279 3 : static int consumeKeySetNode (KeySet * ks, const char * context, xmlTextReaderPtr reader)
280 : {
281 3 : xmlChar * keySetNodeName = xmlTextReaderName (reader);
282 3 : if (!strcmp ((char *) keySetNodeName, "keyset"))
283 : {
284 3 : xmlChar fullContext[800] = "";
285 3 : int end = 0;
286 :
287 3 : xmlChar * privateContext = xmlTextReaderGetAttribute (reader, (const xmlChar *) "parent");
288 3 : if (context && privateContext)
289 : {
290 : /*In libxml earlier than 2.9.4 const char * was used as argument, leading to a warning.
291 : https://git.gnome.org/browse/libxml2/diff/include/libxml/xmlstring.h?id=4472c3a5a5b516aaf59b89be602fbce52756c3e9
292 : */
293 : #pragma GCC diagnostic ignored "-Wpointer-sign"
294 0 : xmlStrPrintf (fullContext, sizeof (fullContext), "%s/%s", context, privateContext);
295 : #pragma GCC diagnostic warning "-Wpointer-sign"
296 : }
297 :
298 : /* Parse everything else */
299 61 : while (!end)
300 : {
301 58 : xmlTextReaderRead (reader);
302 58 : xmlChar * nodeName = xmlTextReaderName (reader);
303 :
304 58 : if (!strcmp ((char *) nodeName, "key"))
305 : {
306 25 : if (privateContext)
307 25 : consumeKeyNode (ks, (char *) (*fullContext ? fullContext : privateContext), reader);
308 : else
309 0 : consumeKeyNode (ks, context, reader);
310 : }
311 33 : else if (!strcmp ((char *) nodeName, "keyset"))
312 : {
313 : /* A <keyset> can have nested <keyset>s */
314 3 : if (xmlTextReaderNodeType (reader) == 15) /* found a </keyset> */
315 : end = 1;
316 0 : else if (privateContext)
317 0 : consumeKeySetNode (ks, (char *) (*fullContext ? fullContext : privateContext), reader);
318 : else
319 0 : consumeKeySetNode (ks, context, reader);
320 : }
321 58 : xmlFree (nodeName);
322 : }
323 3 : if (privateContext) xmlFree (privateContext), privateContext = 0;
324 : }
325 3 : xmlFree (keySetNodeName);
326 3 : return 0;
327 : }
328 :
329 :
330 : /*
331 : * This is the workhorse behind ksFromXML() and ksFromXMLfile().
332 : * It will process the entire XML document in reader and convert and
333 : * save it in ks KeySet. Each node is processed by the processNode() function.
334 : *
335 : * This function is completely dependent on libxml.
336 : */
337 3 : static int ksFromXMLReader (KeySet * ks, xmlTextReaderPtr reader)
338 : {
339 3 : int ret = xmlTextReaderRead (reader); /* go to first node */
340 12 : while (ret == 1)
341 : {
342 : /* walk node per node until the end of the stream */
343 6 : xmlChar * nodeName = xmlTextReaderName (reader);
344 :
345 6 : if (!strcmp ((char *) nodeName, "key"))
346 0 : consumeKeyNode (ks, 0, reader);
347 6 : else if (!strcmp ((char *) nodeName, "keyset"))
348 3 : consumeKeySetNode (ks, 0, reader);
349 :
350 6 : ret = xmlTextReaderRead (reader);
351 :
352 6 : xmlFree (nodeName);
353 : }
354 :
355 3 : return ret;
356 : }
357 :
358 : /*
359 : static int isValidXML(xmlDocPtr doc,char *schemaPath)
360 : {
361 : xmlSchemaPtr wxschemas = NULL;
362 : xmlSchemaValidCtxtPtr ctxt;
363 : xmlSchemaParserCtxtPtr ctxt2=NULL;
364 : int ret=0;
365 :
366 : ctxt2 = xmlSchemaNewParserCtxt(schemaPath);
367 :
368 :
369 : if (ctxt2==NULL) {
370 : xmlFreeDoc(doc);
371 : return 1;
372 : }
373 :
374 : xmlSchemaSetParserErrors(ctxt2,
375 : (xmlSchemaValidityErrorFunc) fprintf,
376 : (xmlSchemaValidityWarningFunc) fprintf,
377 : stderr);
378 : wxschemas = xmlSchemaParse(ctxt2);
379 :
380 : if (wxschemas==NULL) {
381 : xmlSchemaFreeParserCtxt(ctxt2);
382 : xmlFreeDoc(doc);
383 : return 1;
384 : }
385 :
386 : ctxt = xmlSchemaNewValidCtxt(wxschemas);
387 : xmlSchemaSetValidErrors(ctxt,
388 : (xmlSchemaValidityErrorFunc) fprintf,
389 : (xmlSchemaValidityWarningFunc) fprintf,
390 : stderr);
391 :
392 : if (ctxt==NULL) {
393 : xmlSchemaFree(wxschemas);
394 : xmlSchemaFreeParserCtxt(ctxt2);
395 : xmlFreeDoc(doc);
396 : return 1;
397 : }
398 :
399 : ret = xmlSchemaValidateDoc(ctxt, doc);
400 : xmlSchemaFreeValidCtxt(ctxt);
401 : xmlSchemaFree(wxschemas);
402 : xmlSchemaFreeParserCtxt(ctxt2);
403 :
404 : return ret;
405 : }
406 : */
407 :
408 :
409 : /**
410 : * Given an XML @p filename, open it, validate schema, process nodes,
411 : * convert and save it in the @p ks KeySet.
412 : *
413 : * Currently, the XML file can have many root @c @<keyset@> and @c @<key@> nodes.
414 : * They will all be reduced to simple keys returned in @p ks.
415 : *
416 : * To check if the xml file is valid (best before you read from it):
417 : * @code
418 : #include
419 : char schemaPath[513];
420 : schemaPath[0]=0;
421 : ret=kdbGetString(handle, KDB_SCHEMA_PATH_KEY,schemaPath,sizeof(schemaPath));
422 :
423 : if (ret==0) ret = isValidXML(filename,schemaPath);
424 : else ret = isValidXML(filename,KDB_SCHEMA_PATH);
425 : * @endcode
426 : *
427 : * @retval -1 on error
428 : * @retval 0 if file could not be opened
429 : * @param ks the keyset
430 : * @param filename the file to parse
431 : * @ingroup stream
432 : */
433 3 : int ksFromXMLfile (KeySet * ks, const char * filename)
434 : {
435 3 : xmlTextReaderPtr reader = 0;
436 3 : xmlDocPtr doc = 0;
437 3 : int ret = 0;
438 :
439 3 : doc = xmlParseFile (filename);
440 3 : if (doc == 0)
441 : {
442 : // TODO: distinguish between parser errors and
443 : // permission errors?
444 0 : xmlCleanupParser ();
445 0 : return -1;
446 : }
447 :
448 3 : reader = xmlReaderWalker (doc);
449 3 : if (reader)
450 : {
451 3 : ret = ksFromXMLReader (ks, reader);
452 3 : xmlFreeTextReader (reader);
453 : }
454 : else
455 : {
456 : ret = -1;
457 : }
458 :
459 3 : xmlFreeDoc (doc);
460 :
461 3 : xmlCleanupParser ();
462 3 : return ret;
463 : }
464 :
465 :
466 : /**
467 : * Given a file descriptor (that can be @p stdin) for an XML file, validate
468 : * schema, process nodes, convert and save it in the @p ks KeySet.
469 : *
470 : * @param ks keyset
471 : * @param fd POSIX file descriptor
472 : * @ingroup stream
473 : */
474 0 : int ksFromXML (KeySet * ks, int fd)
475 : {
476 : // a complete XML document is expected
477 0 : xmlTextReaderPtr reader = 0;
478 : int ret;
479 0 : reader = xmlReaderForFd (fd, "file:/tmp/imp.xml", 0, 0);
480 0 : if (reader)
481 : {
482 0 : ret = ksFromXMLReader (ks, reader);
483 : }
484 : else
485 : {
486 0 : printf ("kdb: Unable to open file descriptor %d for XML reading\n", fd);
487 0 : return 1;
488 : }
489 0 : return ret;
490 : }
|