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 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
28static XML_Memory_Handling_Suite memsuite =
29{
30  dbus_malloc,
31  dbus_realloc,
32  dbus_free
33};
34
35typedef struct
36{
37  BusConfigParser *parser;
38  const char *filename;
39  DBusString content;
40  DBusError *error;
41  dbus_bool_t failed;
42} ExpatParseContext;
43
44static dbus_bool_t
45process_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
65static void
66expat_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
127static void
128expat_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. */
146static void
147expat_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
165BusConfigParser*
166bus_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}
295