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