2003-03-31 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / bus / config-loader-expat.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* config-loader-expat.c  expat XML loader
3  *
4  * Copyright (C) 2003 Red Hat, Inc.
5  *
6  * Licensed under the Academic Free License version 1.2
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23
24 #include "config-parser.h"
25 #include <dbus/dbus-internals.h>
26 #include <expat.h>
27
28 static XML_Memory_Handling_Suite memsuite =
29 {
30   dbus_malloc,
31   dbus_realloc,
32   dbus_free
33 };
34
35 typedef struct
36 {
37   BusConfigParser *parser;
38   const char *filename;
39   DBusString content;
40   DBusError *error;
41   dbus_bool_t failed;
42 } ExpatParseContext;
43
44 static dbus_bool_t
45 process_content (ExpatParseContext *context)
46 {
47   if (context->failed)
48     return FALSE;
49
50   if (_dbus_string_get_length (&context->content) > 0)
51     {
52       if (!bus_config_parser_content (context->parser,
53                                       &context->content,
54                                       context->error))
55         {
56           context->failed = TRUE;
57           return FALSE;
58         }
59       _dbus_string_set_length (&context->content, 0);
60     }
61
62   return TRUE;
63 }
64
65 static void
66 expat_StartElementHandler (void            *userData,
67                            const XML_Char  *name,
68                            const XML_Char **atts)
69 {
70   ExpatParseContext *context = userData;
71   int i;
72   char **names;
73   char **values;
74
75   /* Expat seems to suck and can't abort the parse if we
76    * throw an error. Expat 2.0 is supposed to fix this.
77    */
78   if (context->failed)
79     return;
80
81   if (!process_content (context))
82     return;
83
84   /* "atts" is key, value, key, value, NULL */
85   for (i = 0; atts[i] != NULL; ++i)
86     ; /* nothing */
87
88   _dbus_assert (i % 2 == 0);
89   names = dbus_new0 (char *, i / 2 + 1);
90   values = dbus_new0 (char *, i / 2 + 1);
91
92   if (names == NULL || values == NULL)
93     {
94       dbus_set_error (context->error, DBUS_ERROR_NO_MEMORY, NULL);
95       context->failed = TRUE;
96       dbus_free (names);
97       dbus_free (values);
98       return;
99     }
100
101   i = 0;
102   while (atts[i] != NULL)
103     {
104       _dbus_assert (i % 2 == 0);
105       names [i / 2] = (char*) atts[i];
106       values[i / 2] = (char*) atts[i+1];
107
108       i += 2;
109     }
110
111   if (!bus_config_parser_start_element (context->parser,
112                                         name,
113                                         (const char **) names,
114                                         (const char **) values,
115                                         context->error))
116     {
117       dbus_free (names);
118       dbus_free (values);
119       context->failed = TRUE;
120       return;
121     }
122
123   dbus_free (names);
124   dbus_free (values);
125 }
126
127 static void
128 expat_EndElementHandler (void           *userData,
129                          const XML_Char *name)
130 {
131   ExpatParseContext *context = userData;
132
133   if (!process_content (context))
134     return;
135
136   if (!bus_config_parser_end_element (context->parser,
137                                       name,
138                                       context->error))
139     {
140       context->failed = TRUE;
141       return;
142     }
143 }
144
145 /* s is not 0 terminated. */
146 static void
147 expat_CharacterDataHandler (void           *userData,
148                             const XML_Char *s,
149                             int             len)
150 {
151   ExpatParseContext *context = userData;
152   if (context->failed)
153     return;
154
155   if (!_dbus_string_append_len (&context->content,
156                                 s, len))
157     {
158       dbus_set_error (context->error, DBUS_ERROR_NO_MEMORY, NULL);
159       context->failed = TRUE;
160       return;
161     }
162 }
163
164
165 BusConfigParser*
166 bus_config_load (const DBusString *file,
167                  DBusError        *error)
168 {
169   XML_Parser expat;
170   const char *filename;
171   BusConfigParser *parser;
172   ExpatParseContext context;
173
174   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
175
176   parser = NULL;
177   expat = NULL;
178   context.error = error;
179   context.failed = FALSE;
180
181   filename = _dbus_string_get_const_data (file);
182
183   if (!_dbus_string_init (&context.content))
184     {
185       dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
186       return NULL;
187     }
188
189   expat = XML_ParserCreate_MM ("UTF-8", &memsuite, NULL);
190   if (expat == NULL)
191     {
192       dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
193       goto failed;
194     }
195
196   parser = bus_config_parser_new ();
197   if (parser == NULL)
198     {
199       dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
200       goto failed;
201     }
202   context.parser = parser;
203
204   XML_SetUserData (expat, &context);
205   XML_SetElementHandler (expat,
206                          expat_StartElementHandler,
207                          expat_EndElementHandler);
208   XML_SetCharacterDataHandler (expat,
209                                expat_CharacterDataHandler);
210
211   {
212     DBusString data;
213     const char *data_str;
214
215     if (!_dbus_string_init (&data))
216       {
217         dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
218         goto failed;
219       }
220
221     if (!_dbus_file_get_contents (&data, file, error))
222       {
223         _dbus_string_free (&data);
224         goto failed;
225       }
226
227     data_str = _dbus_string_get_const_data (&data);
228
229     if (!XML_Parse (expat, data_str, _dbus_string_get_length (&data), TRUE))
230       {
231         if (context.error != NULL &&
232             !dbus_error_is_set (context.error))
233           {
234             enum XML_Error e;
235
236             e = XML_GetErrorCode (expat);
237             if (e == XML_ERROR_NO_MEMORY)
238               dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
239             else
240               dbus_set_error (error, DBUS_ERROR_FAILED,
241                               "Error in file %s, line %d, column %d: %s\n",
242                               filename,
243                               XML_GetCurrentLineNumber (expat),
244                               XML_GetCurrentColumnNumber (expat),
245                               XML_ErrorString (e));
246           }
247
248         _dbus_string_free (&data);
249         goto failed;
250       }
251
252     _dbus_string_free (&data);
253
254     if (context.failed)
255       goto failed;
256   }
257
258   if (!bus_config_parser_finished (parser, error))
259     goto failed;
260
261   _dbus_string_free (&context.content);
262   XML_ParserFree (expat);
263
264   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
265   return parser;
266
267  failed:
268   _DBUS_ASSERT_ERROR_IS_SET (error);
269
270   _dbus_string_free (&context.content);
271   if (expat)
272     XML_ParserFree (expat);
273   if (parser)
274     bus_config_parser_unref (parser);
275   return NULL;
276 }