2003-04-02 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   DBusString dirname;
174   
175   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
176
177   parser = NULL;
178   expat = NULL;
179   context.error = error;
180   context.failed = FALSE;
181
182   filename = _dbus_string_get_const_data (file);
183
184   if (!_dbus_string_init (&context.content))
185     {
186       dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
187       return NULL;
188     }
189
190   if (!_dbus_string_init (&dirname))
191     {
192       dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
193       _dbus_string_free (&context.content);
194       return NULL;
195     }
196   
197   expat = XML_ParserCreate_MM ("UTF-8", &memsuite, NULL);
198   if (expat == NULL)
199     {
200       dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
201       goto failed;
202     }
203
204   if (!_dbus_string_get_dirname (file, &dirname))
205     {
206       dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
207       goto failed;
208     }
209   
210   parser = bus_config_parser_new (&dirname);
211   if (parser == NULL)
212     {
213       dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
214       goto failed;
215     }
216   context.parser = parser;
217
218   XML_SetUserData (expat, &context);
219   XML_SetElementHandler (expat,
220                          expat_StartElementHandler,
221                          expat_EndElementHandler);
222   XML_SetCharacterDataHandler (expat,
223                                expat_CharacterDataHandler);
224
225   {
226     DBusString data;
227     const char *data_str;
228
229     if (!_dbus_string_init (&data))
230       {
231         dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
232         goto failed;
233       }
234
235     if (!_dbus_file_get_contents (&data, file, error))
236       {
237         _dbus_string_free (&data);
238         goto failed;
239       }
240
241     data_str = _dbus_string_get_const_data (&data);
242
243     if (!XML_Parse (expat, data_str, _dbus_string_get_length (&data), TRUE))
244       {
245         if (context.error != NULL &&
246             !dbus_error_is_set (context.error))
247           {
248             enum XML_Error e;
249
250             e = XML_GetErrorCode (expat);
251             if (e == XML_ERROR_NO_MEMORY)
252               dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
253             else
254               dbus_set_error (error, DBUS_ERROR_FAILED,
255                               "Error in file %s, line %d, column %d: %s\n",
256                               filename,
257                               XML_GetCurrentLineNumber (expat),
258                               XML_GetCurrentColumnNumber (expat),
259                               XML_ErrorString (e));
260           }
261
262         _dbus_string_free (&data);
263         goto failed;
264       }
265
266     _dbus_string_free (&data);
267
268     if (context.failed)
269       goto failed;
270   }
271
272   if (!bus_config_parser_finished (parser, error))
273     goto failed;
274
275   _dbus_string_free (&dirname);
276   _dbus_string_free (&context.content);
277   XML_ParserFree (expat);
278
279   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
280   return parser;
281
282  failed:
283   _DBUS_ASSERT_ERROR_IS_SET (error);
284
285   _dbus_string_free (&dirname);
286   _dbus_string_free (&context.content);
287   if (expat)
288     XML_ParserFree (expat);
289   if (parser)
290     bus_config_parser_unref (parser);
291   return NULL;
292 }