2003-02-12 Anders Carlsson <andersca@codefactory.se>
[platform/upstream/dbus.git] / bus / desktop-file.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* desktop-file.c  .desktop file parser
3  *
4  * Copyright (C) 2003  CodeFactory AB
5  * Copyright (C) 2003  Red Hat Inc.
6  *
7  * Licensed under the Academic Free License version 1.2
8  * 
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  * 
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  *
23  */
24 #include <dbus/dbus-sysdeps.h>
25 #include <dbus/dbus-internals.h>
26 #include "desktop-file.h"
27
28 typedef enum 
29 {
30   BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX,
31   BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES,
32   BUS_DESKTOP_PARSE_ERROR_INVALID_CHARS
33 } BusDesktopParseError;
34
35 typedef struct
36 {
37   char *key;
38   char *value;
39 } BusDesktopFileLine;
40
41 typedef struct
42 {
43   char *section_name;
44   
45   int n_lines;
46   BusDesktopFileLine *lines;
47   int n_allocated_lines;  
48 } BusDesktopFileSection;
49
50 struct BusDesktopFile
51 {
52   int n_sections;
53   BusDesktopFileSection *sections;
54   int n_allocated_sections;
55 };
56
57 typedef struct
58 {
59   DBusString data;
60
61   BusDesktopFile *desktop_file;
62   int current_section;
63   
64   int pos, len;
65   int line_num;
66   
67 } BusDesktopFileParser;
68
69 #define VALID_KEY_CHAR 1
70 #define VALID_LOCALE_CHAR 2
71 unsigned char valid[256] = { 
72    0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 
73    0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 
74    0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x3 , 0x2 , 0x0 , 
75    0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 
76    0x0 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 
77    0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x2 , 
78    0x0 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 
79    0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 
80    0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 
81    0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 
82    0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 
83    0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 
84    0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 
85    0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 
86    0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 
87    0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 
88 };
89
90 static void report_error (BusDesktopFileParser   *parser,
91                           char                   *message,
92                           BusDesktopParseError    error_code);
93
94 static void
95 parser_free (BusDesktopFileParser *parser)
96 {
97   bus_desktop_file_free (parser->desktop_file);
98   
99   _dbus_string_free (&parser->data);
100 }
101
102 static void
103 bus_desktop_file_line_free (BusDesktopFileLine *line)
104 {
105   dbus_free (line->key);
106   dbus_free (line->value);
107 }
108
109 static void
110 bus_desktop_file_section_free (BusDesktopFileSection *section)
111 {
112   int i;
113
114   for (i = 0; i < section->n_lines; i++)
115     bus_desktop_file_line_free (&section->lines[i]);
116
117   dbus_free (section->lines);
118   dbus_free (section->section_name);
119 }
120
121 void
122 bus_desktop_file_free (BusDesktopFile *desktop_file)
123 {
124   int i;
125
126   for (i = 0; i < desktop_file->n_sections; i++)
127     bus_desktop_file_section_free (&desktop_file->sections[i]);
128   dbus_free (desktop_file->sections);
129
130   dbus_free (desktop_file);
131 }
132
133 static void
134 grow_lines_in_section (BusDesktopFileSection *section)
135 {
136   BusDesktopFileLine *lines;
137   
138   int new_n_lines;
139
140   if (section->n_allocated_lines == 0)
141     new_n_lines = 1;
142   else
143     new_n_lines = section->n_allocated_lines*2;
144
145   _DBUS_HANDLE_OOM (lines = dbus_realloc (section->lines,
146                                           sizeof (BusDesktopFileLine) * new_n_lines));
147   section->lines = lines;
148   
149   section->n_allocated_lines = new_n_lines;
150 }
151
152 static void
153 grow_sections (BusDesktopFile *desktop_file)
154 {
155   int new_n_sections;
156   BusDesktopFileSection *sections;
157   
158   if (desktop_file->n_allocated_sections == 0)
159     new_n_sections = 1;
160   else
161     new_n_sections = desktop_file->n_allocated_sections*2;
162
163   _DBUS_HANDLE_OOM (sections = dbus_realloc (desktop_file->sections,
164                                              sizeof (BusDesktopFileSection) * new_n_sections));
165   desktop_file->sections = sections;
166   
167   desktop_file->n_allocated_sections = new_n_sections;
168 }
169
170 static char *
171 unescape_string (const DBusString *str, int pos, int end_pos)
172 {
173   char *retval, *q;
174   
175   /* len + 1 is enough, because unescaping never makes the
176    * string longer */
177   _DBUS_HANDLE_OOM (retval = dbus_malloc (end_pos - pos + 1));
178
179   q = retval;
180   
181   while (pos < end_pos)
182     {
183       if (_dbus_string_get_byte (str, pos) == 0)
184         {
185           /* Found an embedded null */
186           dbus_free (retval);
187           return NULL;
188         }
189
190       if (_dbus_string_get_byte (str, pos) == '\\')
191         {
192           pos ++;
193
194           if (pos >= end_pos)
195             {
196               /* Escape at end of string */
197               dbus_free (retval);
198               return NULL;
199             }
200
201           switch (_dbus_string_get_byte (str, pos))
202             {
203             case 's':
204               *q++ = ' ';
205               break;
206            case 't':
207               *q++ = '\t';
208               break;
209            case 'n':
210               *q++ = '\n';
211               break;
212            case 'r':
213               *q++ = '\r';
214               break;
215            case '\\':
216               *q++ = '\\';
217               break;
218            default:
219              /* Invalid escape code */
220              dbus_free (retval);
221              return NULL;
222             }
223           pos++;
224         }
225       else
226         {
227           *q++ =_dbus_string_get_byte (str, pos);
228
229           pos++;
230         }
231     }
232
233   *q = 0;
234
235   return retval;
236 }
237
238 static BusDesktopFileSection* 
239 new_section (BusDesktopFile *desktop_file,
240              const char     *name)
241 {
242   int n;
243   
244   if (desktop_file->n_allocated_sections == desktop_file->n_sections)
245     grow_sections (desktop_file);
246
247   n = desktop_file->n_sections++;
248
249   _DBUS_HANDLE_OOM (desktop_file->sections[n].section_name = _dbus_strdup (name));
250
251   desktop_file->sections[n].n_lines = 0;
252   desktop_file->sections[n].lines = NULL;
253   desktop_file->sections[n].n_allocated_lines = 0;
254
255   grow_lines_in_section (&desktop_file->sections[n]);
256
257   return &desktop_file->sections[n];  
258 }
259
260 static BusDesktopFileSection* 
261 open_section (BusDesktopFileParser *parser,
262               char                 *name)
263 {  
264   BusDesktopFileSection *section;
265
266   section = new_section (parser->desktop_file, name);
267   if (section == NULL)
268     return NULL;
269   
270   parser->current_section = parser->desktop_file->n_sections - 1;
271   _dbus_assert (&parser->desktop_file->sections[parser->current_section] == section);
272   
273   return section;
274 }
275
276 static BusDesktopFileLine *
277 new_line (BusDesktopFileParser *parser)
278 {
279   BusDesktopFileSection *section;
280   BusDesktopFileLine *line;
281   
282   section = &parser->desktop_file->sections[parser->current_section];
283
284   if (section->n_allocated_lines == section->n_lines)
285     grow_lines_in_section (section);
286
287   line = &section->lines[section->n_lines++];
288
289   memset (line, 0, sizeof (BusDesktopFileLine));
290     
291   return line;
292 }
293
294 static dbus_bool_t
295 is_blank_line (BusDesktopFileParser *parser)
296 {
297   int p;
298   char c;
299   
300   p = parser->pos;
301
302   c = _dbus_string_get_byte (&parser->data, p);
303
304   while (c && c != '\n')
305     {
306       if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f'))
307         return FALSE;
308       
309       p++;
310       c = _dbus_string_get_byte (&parser->data, p);
311     }
312
313   return TRUE;
314 }
315
316 static void
317 parse_comment_or_blank (BusDesktopFileParser *parser)
318 {
319   int line_end;
320   
321   if (!_dbus_string_find (&parser->data, parser->pos, "\n", &line_end))
322     line_end = parser->len;
323
324   if (line_end == parser->len)
325     parser->pos = parser->len;
326   else
327     parser->pos = line_end + 1;
328   
329   parser->line_num += 1;
330 }
331
332 static dbus_bool_t
333 is_valid_section_name (const char *name)
334 {
335   /* 5. Group names may contain all ASCII characters except for control characters and '[' and ']'. */
336
337   while (*name)
338     {
339       if (!((*name >= 'A' && *name <= 'Z') || (*name >= 'a' || *name <= 'z') ||
340             *name == '\n' || *name == '\t'))
341         return FALSE;
342       
343       name++;
344     }
345
346   return TRUE;
347 }
348
349 static dbus_bool_t
350 parse_section_start (BusDesktopFileParser *parser, DBusResultCode *result)
351 {
352   int line_end;
353   char *section_name;
354   
355   if (!_dbus_string_find (&parser->data, parser->pos, "\n", &line_end))
356     line_end = parser->len;
357   
358   if (line_end - parser->pos <= 2 ||
359       _dbus_string_get_byte (&parser->data, line_end - 1) != ']')
360     {
361       report_error (parser, "Invalid syntax for section header", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX);
362       parser_free (parser);
363       return FALSE;
364     }
365
366   section_name = unescape_string (&parser->data, parser->pos + 1, line_end - 1);
367
368   if (section_name == NULL)
369     {
370       report_error (parser, "Invalid escaping in section name", BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES);
371       parser_free (parser);
372       return FALSE;
373     }
374
375   if (!is_valid_section_name (section_name))
376     {
377       report_error (parser, "Invalid characters in section name", BUS_DESKTOP_PARSE_ERROR_INVALID_CHARS);
378       parser_free (parser);
379       dbus_free (section_name);
380       return FALSE;
381     }
382
383   if (open_section (parser, section_name) == NULL)
384     {
385       dbus_free (section_name);
386       return FALSE;
387     }
388
389   if (line_end == parser->len)
390     parser->pos = parser->len;
391   else
392     parser->pos = line_end + 1;
393   
394   parser->line_num += 1;
395
396   dbus_free (section_name);
397   
398   return TRUE;
399 }
400
401 static dbus_bool_t
402 parse_key_value (BusDesktopFileParser *parser, DBusResultCode *result)
403 {
404   int line_end;
405   int key_start, key_end;
406   int value_start;
407   int p;
408   char *value, *tmp;
409   DBusString key;
410   BusDesktopFileLine *line;
411   
412   if (!_dbus_string_find (&parser->data, parser->pos, "\n", &line_end))
413     line_end = parser->len;
414
415   p = parser->pos;
416   key_start = p;
417   while (p < line_end &&
418          (valid[_dbus_string_get_byte (&parser->data, p)] & VALID_KEY_CHAR))
419     p++;
420   key_end = p;
421   
422   if (key_start == key_end)
423     {
424       report_error (parser, "Empty key name", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX);
425       parser_free (parser);
426       return FALSE;
427     }
428
429   /* We ignore locales for now */
430   
431   /* Skip space before '=' */
432   while (p < line_end && _dbus_string_get_byte (&parser->data, p) == ' ')
433     p++;
434
435   if (p < line_end && _dbus_string_get_byte (&parser->data, p) != '=')
436     {
437       report_error (parser, "Invalid characters in key name", BUS_DESKTOP_PARSE_ERROR_INVALID_CHARS);
438       parser_free (parser);
439       return FALSE;
440     }
441
442   if (p == line_end)
443     {
444       report_error (parser, "No '=' in key/value pair", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX);
445       parser_free (parser);
446       return FALSE;
447     }
448
449   /* Skip the '=' */
450   p++;
451
452   /* Skip space after '=' */
453   while (p < line_end && _dbus_string_get_byte (&parser->data, p) == ' ')
454     p++;
455
456   value_start = p;
457   
458   value = unescape_string (&parser->data, value_start, line_end);
459   if (value == NULL)
460     {
461       report_error (parser, "Invalid escaping in value", BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES);
462       parser_free (parser);
463       return FALSE;
464     }
465
466   line = new_line (parser);
467
468   _DBUS_HANDLE_OOM (_dbus_string_init (&key, key_end - key_start));
469   _DBUS_HANDLE_OOM (_dbus_string_copy_len (&parser->data, key_start, key_end - key_start,
470                                            &key, 0));
471   _DBUS_HANDLE_OOM (_dbus_string_steal_data (&key, &tmp));
472   _dbus_string_free (&key);
473   
474   line->key = tmp;
475   line->value = value;
476
477   if (line_end == parser->len)
478     parser->pos = parser->len;
479   else
480     parser->pos = line_end + 1;
481   
482   parser->line_num += 1;
483
484   return TRUE;
485 }
486
487 static void
488 report_error (BusDesktopFileParser   *parser,
489               char                   *message,
490               BusDesktopParseError    error_code)
491 {
492   char *section_name = NULL;
493   
494   /* FIXME: */
495   section_name = NULL;
496
497   if (section_name)
498     _dbus_verbose ("Error in section %s at line %d: %s\n", section_name, parser->line_num, message);
499   else
500     _dbus_verbose ("Error at line %d: %s\n", parser->line_num, message);
501 }
502
503 #if 0
504 static void
505 dump_desktop_file (BusDesktopFile *file)
506 {
507   int i;
508
509   for (i = 0; i < file->n_sections; i++)
510     {
511       int j;
512       
513       printf ("[%s]\n", file->sections[i].section_name);
514
515       for (j = 0; j < file->sections[i].n_lines; j++)
516         {
517           printf ("%s=%s\n", file->sections[i].lines[j].key,
518                   file->sections[i].lines[j].value);
519         }
520     }
521 }
522 #endif
523
524 BusDesktopFile *
525 bus_desktop_file_load (const char     *filename,
526                        DBusResultCode *result)
527 {
528   DBusString str, filename_str;
529   DBusResultCode result_code;
530   BusDesktopFileParser parser;
531   
532   /* FIXME: Check file size so we don't try to load a ridicously large file. */
533
534   _dbus_string_init_const (&filename_str, filename);
535
536   _DBUS_HANDLE_OOM (_dbus_string_init (&str, _DBUS_INT_MAX));
537   
538   _DBUS_HANDLE_OOM ((result_code = _dbus_file_get_contents (&str, &filename_str)) !=
539                     DBUS_RESULT_NO_MEMORY);
540
541   if (result_code != DBUS_RESULT_SUCCESS)
542     {
543       _dbus_string_free (&str);
544       dbus_set_result (result, result_code);
545       return NULL;
546     }
547
548   if (!_dbus_string_validate_utf8 (&str, 0, _dbus_string_get_length (&str)))
549     {
550       _dbus_string_free (&str);
551       /* FIXME: Use a better error code */
552       dbus_set_result (result, result_code);
553       return NULL;
554     }
555   
556   _DBUS_HANDLE_OOM (parser.desktop_file = dbus_malloc (sizeof (BusDesktopFile)));
557
558   memset (parser.desktop_file, 0, sizeof (BusDesktopFile));
559   
560   parser.data = str;
561   parser.line_num = 1;
562   parser.pos = 0;
563   parser.len = _dbus_string_get_length (&parser.data);
564   
565   while (parser.pos < parser.len)
566     {
567       if (_dbus_string_get_byte (&parser.data, parser.pos) == '[')
568         {
569           if (!parse_section_start (&parser, result))
570             return NULL;
571         }
572       else if (is_blank_line (&parser) ||
573                _dbus_string_get_byte (&parser.data, parser.pos) == '#')
574         parse_comment_or_blank (&parser);
575       else
576         {
577           if (!parse_key_value (&parser, result))
578             return NULL;
579         }
580     }
581
582   _dbus_string_free (&parser.data);
583
584   dbus_set_result (result, DBUS_RESULT_SUCCESS);
585   
586   return parser.desktop_file;
587 }
588