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