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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21 *
22 */
23
24#include <config.h>
25#include "config-parser.h"
26#include <dbus/dbus-internals.h>
27#include <expat.h>
28
29static XML_Memory_Handling_Suite memsuite;
30
31typedef struct
32{
33  BusConfigParser *parser;
34  const char *filename;
35  DBusString content;
36  DBusError *error;
37  dbus_bool_t failed;
38} ExpatParseContext;
39
40static dbus_bool_t
41process_content (ExpatParseContext *context)
42{
43  if (context->failed)
44    return FALSE;
45
46  if (_dbus_string_get_length (&context->content) > 0)
47    {
48      if (!bus_config_parser_content (context->parser,
49                                      &context->content,
50                                      context->error))
51        {
52          context->failed = TRUE;
53          return FALSE;
54        }
55      _dbus_string_set_length (&context->content, 0);
56    }
57
58  return TRUE;
59}
60
61static void
62expat_StartElementHandler (void            *userData,
63                           const XML_Char  *name,
64                           const XML_Char **atts)
65{
66  ExpatParseContext *context = userData;
67  int i;
68  char **names;
69  char **values;
70
71  /* Expat seems to suck and can't abort the parse if we
72   * throw an error. Expat 2.0 is supposed to fix this.
73   */
74  if (context->failed)
75    return;
76
77  if (!process_content (context))
78    return;
79
80  /* "atts" is key, value, key, value, NULL */
81  for (i = 0; atts[i] != NULL; ++i)
82    ; /* nothing */
83
84  _dbus_assert (i % 2 == 0);
85  names = dbus_new0 (char *, i / 2 + 1);
86  values = dbus_new0 (char *, i / 2 + 1);
87
88  if (names == NULL || values == NULL)
89    {
90      dbus_set_error (context->error, DBUS_ERROR_NO_MEMORY, NULL);
91      context->failed = TRUE;
92      dbus_free (names);
93      dbus_free (values);
94      return;
95    }
96
97  i = 0;
98  while (atts[i] != NULL)
99    {
100      _dbus_assert (i % 2 == 0);
101      names [i / 2] = (char*) atts[i];
102      values[i / 2] = (char*) atts[i+1];
103
104      i += 2;
105    }
106
107  if (!bus_config_parser_start_element (context->parser,
108                                        name,
109                                        (const char **) names,
110                                        (const char **) values,
111                                        context->error))
112    {
113      dbus_free (names);
114      dbus_free (values);
115      context->failed = TRUE;
116      return;
117    }
118
119  dbus_free (names);
120  dbus_free (values);
121}
122
123static void
124expat_EndElementHandler (void           *userData,
125                         const XML_Char *name)
126{
127  ExpatParseContext *context = userData;
128
129  if (!process_content (context))
130    return;
131
132  if (!bus_config_parser_end_element (context->parser,
133                                      name,
134                                      context->error))
135    {
136      context->failed = TRUE;
137      return;
138    }
139}
140
141/* s is not 0 terminated. */
142static void
143expat_CharacterDataHandler (void           *userData,
144                            const XML_Char *s,
145                            int             len)
146{
147  ExpatParseContext *context = userData;
148  if (context->failed)
149    return;
150
151  if (!_dbus_string_append_len (&context->content,
152                                s, len))
153    {
154      dbus_set_error (context->error, DBUS_ERROR_NO_MEMORY, NULL);
155      context->failed = TRUE;
156      return;
157    }
158}
159
160
161BusConfigParser*
162bus_config_load (const DBusString      *file,
163                 dbus_bool_t            is_toplevel,
164                 const BusConfigParser *parent,
165                 DBusError             *error)
166{
167  XML_Parser expat;
168  const char *filename;
169  BusConfigParser *parser;
170  ExpatParseContext context;
171  DBusString dirname;
172
173  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
174
175  parser = NULL;
176  expat = NULL;
177  context.error = error;
178  context.failed = FALSE;
179  filename = _dbus_string_get_const_data (file);
180
181  if (!_dbus_string_init (&context.content))
182    {
183      dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
184      return NULL;
185    }
186
187  if (!_dbus_string_init (&dirname))
188    {
189      dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
190      _dbus_string_free (&context.content);
191      return NULL;
192    }
193
194  memsuite.malloc_fcn = dbus_malloc;
195  memsuite.realloc_fcn = dbus_realloc;
196  memsuite.free_fcn = dbus_free;
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, parent);
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}
294