2003-02-13 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                           DBusResultCode         *result);
94
95 static void
96 parser_free (BusDesktopFileParser *parser)
97 {
98   bus_desktop_file_free (parser->desktop_file);
99   
100   _dbus_string_free (&parser->data);
101 }
102
103 static void
104 bus_desktop_file_line_free (BusDesktopFileLine *line)
105 {
106   dbus_free (line->key);
107   dbus_free (line->value);
108 }
109
110 static void
111 bus_desktop_file_section_free (BusDesktopFileSection *section)
112 {
113   int i;
114
115   for (i = 0; i < section->n_lines; i++)
116     bus_desktop_file_line_free (&section->lines[i]);
117
118   dbus_free (section->lines);
119   dbus_free (section->section_name);
120 }
121
122 void
123 bus_desktop_file_free (BusDesktopFile *desktop_file)
124 {
125   int i;
126
127   for (i = 0; i < desktop_file->n_sections; i++)
128     bus_desktop_file_section_free (&desktop_file->sections[i]);
129   dbus_free (desktop_file->sections);
130
131   dbus_free (desktop_file);
132 }
133
134 static void
135 grow_lines_in_section (BusDesktopFileSection *section)
136 {
137   BusDesktopFileLine *lines;
138   
139   int new_n_lines;
140
141   if (section->n_allocated_lines == 0)
142     new_n_lines = 1;
143   else
144     new_n_lines = section->n_allocated_lines*2;
145
146   _DBUS_HANDLE_OOM (lines = dbus_realloc (section->lines,
147                                           sizeof (BusDesktopFileLine) * new_n_lines));
148   section->lines = lines;
149   
150   section->n_allocated_lines = new_n_lines;
151 }
152
153 static void
154 grow_sections (BusDesktopFile *desktop_file)
155 {
156   int new_n_sections;
157   BusDesktopFileSection *sections;
158   
159   if (desktop_file->n_allocated_sections == 0)
160     new_n_sections = 1;
161   else
162     new_n_sections = desktop_file->n_allocated_sections*2;
163
164   _DBUS_HANDLE_OOM (sections = dbus_realloc (desktop_file->sections,
165                                              sizeof (BusDesktopFileSection) * new_n_sections));
166   desktop_file->sections = sections;
167   
168   desktop_file->n_allocated_sections = new_n_sections;
169 }
170
171 static char *
172 unescape_string (const DBusString *str, int pos, int end_pos)
173 {
174   char *retval, *q;
175   
176   /* len + 1 is enough, because unescaping never makes the
177    * string longer */
178   _DBUS_HANDLE_OOM (retval = dbus_malloc (end_pos - pos + 1));
179
180   q = retval;
181   
182   while (pos < end_pos)
183     {
184       if (_dbus_string_get_byte (str, pos) == 0)
185         {
186           /* Found an embedded null */
187           dbus_free (retval);
188           return NULL;
189         }
190
191       if (_dbus_string_get_byte (str, pos) == '\\')
192         {
193           pos ++;
194
195           if (pos >= end_pos)
196             {
197               /* Escape at end of string */
198               dbus_free (retval);
199               return NULL;
200             }
201
202           switch (_dbus_string_get_byte (str, pos))
203             {
204             case 's':
205               *q++ = ' ';
206               break;
207            case 't':
208               *q++ = '\t';
209               break;
210            case 'n':
211               *q++ = '\n';
212               break;
213            case 'r':
214               *q++ = '\r';
215               break;
216            case '\\':
217               *q++ = '\\';
218               break;
219            default:
220              /* Invalid escape code */
221              dbus_free (retval);
222              return NULL;
223             }
224           pos++;
225         }
226       else
227         {
228           *q++ =_dbus_string_get_byte (str, pos);
229
230           pos++;
231         }
232     }
233
234   *q = 0;
235
236   return retval;
237 }
238
239 static BusDesktopFileSection* 
240 new_section (BusDesktopFile *desktop_file,
241              const char     *name)
242 {
243   int n;
244   
245   if (desktop_file->n_allocated_sections == desktop_file->n_sections)
246     grow_sections (desktop_file);
247
248   n = desktop_file->n_sections++;
249
250   _DBUS_HANDLE_OOM (desktop_file->sections[n].section_name = _dbus_strdup (name));
251
252   desktop_file->sections[n].n_lines = 0;
253   desktop_file->sections[n].lines = NULL;
254   desktop_file->sections[n].n_allocated_lines = 0;
255
256   grow_lines_in_section (&desktop_file->sections[n]);
257
258   return &desktop_file->sections[n];  
259 }
260
261 static BusDesktopFileSection* 
262 open_section (BusDesktopFileParser *parser,
263               char                 *name)
264 {  
265   BusDesktopFileSection *section;
266
267   section = new_section (parser->desktop_file, name);
268   if (section == NULL)
269     return NULL;
270   
271   parser->current_section = parser->desktop_file->n_sections - 1;
272   _dbus_assert (&parser->desktop_file->sections[parser->current_section] == section);
273   
274   return section;
275 }
276
277 static BusDesktopFileLine *
278 new_line (BusDesktopFileParser *parser)
279 {
280   BusDesktopFileSection *section;
281   BusDesktopFileLine *line;
282   
283   section = &parser->desktop_file->sections[parser->current_section];
284
285   if (section->n_allocated_lines == section->n_lines)
286     grow_lines_in_section (section);
287
288   line = &section->lines[section->n_lines++];
289
290   memset (line, 0, sizeof (BusDesktopFileLine));
291     
292   return line;
293 }
294
295 static dbus_bool_t
296 is_blank_line (BusDesktopFileParser *parser)
297 {
298   int p;
299   char c;
300   
301   p = parser->pos;
302
303   c = _dbus_string_get_byte (&parser->data, p);
304
305   while (c && c != '\n')
306     {
307       if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f'))
308         return FALSE;
309       
310       p++;
311       c = _dbus_string_get_byte (&parser->data, p);
312     }
313
314   return TRUE;
315 }
316
317 static void
318 parse_comment_or_blank (BusDesktopFileParser *parser)
319 {
320   int line_end;
321   
322   if (!_dbus_string_find (&parser->data, parser->pos, "\n", &line_end))
323     line_end = parser->len;
324
325   if (line_end == parser->len)
326     parser->pos = parser->len;
327   else
328     parser->pos = line_end + 1;
329   
330   parser->line_num += 1;
331 }
332
333 static dbus_bool_t
334 is_valid_section_name (const char *name)
335 {
336   /* 5. Group names may contain all ASCII characters except for control characters and '[' and ']'. */
337
338   while (*name)
339     {
340       if (!((*name >= 'A' && *name <= 'Z') || (*name >= 'a' || *name <= 'z') ||
341             *name == '\n' || *name == '\t'))
342         return FALSE;
343       
344       name++;
345     }
346
347   return TRUE;
348 }
349
350 static dbus_bool_t
351 parse_section_start (BusDesktopFileParser *parser, DBusResultCode *result)
352 {
353   int line_end;
354   char *section_name;
355   
356   if (!_dbus_string_find (&parser->data, parser->pos, "\n", &line_end))
357     line_end = parser->len;
358   
359   if (line_end - parser->pos <= 2 ||
360       _dbus_string_get_byte (&parser->data, line_end - 1) != ']')
361     {
362       report_error (parser, "Invalid syntax for section header", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX, result);
363       parser_free (parser);
364       return FALSE;
365     }
366
367   section_name = unescape_string (&parser->data, parser->pos + 1, line_end - 1);
368
369   if (section_name == NULL)
370     {
371       report_error (parser, "Invalid escaping in section name", BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES, result);
372       parser_free (parser);
373       return FALSE;
374     }
375
376   if (!is_valid_section_name (section_name))
377     {
378       report_error (parser, "Invalid characters in section name", BUS_DESKTOP_PARSE_ERROR_INVALID_CHARS, result);
379       parser_free (parser);
380       dbus_free (section_name);
381       return FALSE;
382     }
383
384   if (open_section (parser, section_name) == NULL)
385     {
386       dbus_free (section_name);
387       return FALSE;
388     }
389
390   if (line_end == parser->len)
391     parser->pos = parser->len;
392   else
393     parser->pos = line_end + 1;
394   
395   parser->line_num += 1;
396
397   dbus_free (section_name);
398   
399   return TRUE;
400 }
401
402 static dbus_bool_t
403 parse_key_value (BusDesktopFileParser *parser, DBusResultCode *result)
404 {
405   int line_end;
406   int key_start, key_end;
407   int value_start;
408   int p;
409   char *value, *tmp;
410   DBusString key;
411   BusDesktopFileLine *line;
412   
413   if (!_dbus_string_find (&parser->data, parser->pos, "\n", &line_end))
414     line_end = parser->len;
415
416   p = parser->pos;
417   key_start = p;
418   while (p < line_end &&
419          (valid[_dbus_string_get_byte (&parser->data, p)] & VALID_KEY_CHAR))
420     p++;
421   key_end = p;
422   
423   if (key_start == key_end)
424     {
425       report_error (parser, "Empty key name", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX, result);
426       parser_free (parser);
427       return FALSE;
428     }
429
430   /* We ignore locales for now */
431   
432   /* Skip space before '=' */
433   while (p < line_end && _dbus_string_get_byte (&parser->data, p) == ' ')
434     p++;
435
436   if (p < line_end && _dbus_string_get_byte (&parser->data, p) != '=')
437     {
438       report_error (parser, "Invalid characters in key name", BUS_DESKTOP_PARSE_ERROR_INVALID_CHARS, result);
439       parser_free (parser);
440       return FALSE;
441     }
442
443   if (p == line_end)
444     {
445       report_error (parser, "No '=' in key/value pair", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX, result);
446       parser_free (parser);
447       return FALSE;
448     }
449
450   /* Skip the '=' */
451   p++;
452
453   /* Skip space after '=' */
454   while (p < line_end && _dbus_string_get_byte (&parser->data, p) == ' ')
455     p++;
456
457   value_start = p;
458   
459   value = unescape_string (&parser->data, value_start, line_end);
460   if (value == NULL)
461     {
462       report_error (parser, "Invalid escaping in value", BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES, result);
463       parser_free (parser);
464       return FALSE;
465     }
466
467   line = new_line (parser);
468
469   _DBUS_HANDLE_OOM (_dbus_string_init (&key, key_end - key_start));
470   _DBUS_HANDLE_OOM (_dbus_string_copy_len (&parser->data, key_start, key_end - key_start,
471                                            &key, 0));
472   _DBUS_HANDLE_OOM (_dbus_string_steal_data (&key, &tmp));
473   _dbus_string_free (&key);
474   
475   line->key = tmp;
476   line->value = value;
477
478   if (line_end == parser->len)
479     parser->pos = parser->len;
480   else
481     parser->pos = line_end + 1;
482   
483   parser->line_num += 1;
484
485   return TRUE;
486 }
487
488 static void
489 report_error (BusDesktopFileParser   *parser,
490               char                   *message,
491               BusDesktopParseError    error_code,
492               DBusResultCode         *result)
493 {
494   const char *section_name = NULL;
495     
496   if (parser->current_section != -1)
497     section_name = parser->desktop_file->sections[parser->current_section].section_name;
498
499   if (section_name)
500     _dbus_verbose ("Error in section %s at line %d: %s\n", section_name, parser->line_num, message);
501   else
502     _dbus_verbose ("Error at line %d: %s\n", parser->line_num, message);
503
504   dbus_set_result (result, DBUS_RESULT_FAILED);
505 }
506
507 #if 0
508 static void
509 dump_desktop_file (BusDesktopFile *file)
510 {
511   int i;
512
513   for (i = 0; i < file->n_sections; i++)
514     {
515       int j;
516       
517       printf ("[%s]\n", file->sections[i].section_name);
518
519       for (j = 0; j < file->sections[i].n_lines; j++)
520         {
521           printf ("%s=%s\n", file->sections[i].lines[j].key,
522                   file->sections[i].lines[j].value);
523         }
524     }
525 }
526 #endif
527
528 BusDesktopFile *
529 bus_desktop_file_load (DBusString     *filename,
530                        DBusResultCode *result)
531 {
532   DBusString str;
533   DBusResultCode result_code;
534   BusDesktopFileParser parser;
535   
536   /* FIXME: Check file size so we don't try to load a ridicously large file. */
537
538   _DBUS_HANDLE_OOM (_dbus_string_init (&str, _DBUS_INT_MAX));
539   
540   _DBUS_HANDLE_OOM ((result_code = _dbus_file_get_contents (&str, filename)) !=
541                     DBUS_RESULT_NO_MEMORY);
542
543   if (result_code != DBUS_RESULT_SUCCESS)
544     {
545       _dbus_string_free (&str);
546       dbus_set_result (result, result_code);
547       return NULL;
548     }
549
550   if (!_dbus_string_validate_utf8 (&str, 0, _dbus_string_get_length (&str)))
551     {
552       _dbus_string_free (&str);
553       
554       /* FIXME: Use a better error code */
555       dbus_set_result (result, DBUS_RESULT_FAILED);
556       return NULL;
557     }
558   
559   _DBUS_HANDLE_OOM (parser.desktop_file = dbus_malloc0 (sizeof (BusDesktopFile)));
560
561   parser.data = str;
562   parser.line_num = 1;
563   parser.pos = 0;
564   parser.len = _dbus_string_get_length (&parser.data);
565   parser.current_section = -1;
566   
567   while (parser.pos < parser.len)
568     {
569       if (_dbus_string_get_byte (&parser.data, parser.pos) == '[')
570         {
571           if (!parse_section_start (&parser, result))
572             return NULL;
573         }
574       else if (is_blank_line (&parser) ||
575                _dbus_string_get_byte (&parser.data, parser.pos) == '#')
576         parse_comment_or_blank (&parser);
577       else
578         {
579           if (!parse_key_value (&parser, result))
580             return NULL;
581         }
582     }
583
584   _dbus_string_free (&parser.data);
585
586   dbus_set_result (result, DBUS_RESULT_SUCCESS);
587   
588   return parser.desktop_file;
589 }
590
591 static BusDesktopFileSection *
592 lookup_section (BusDesktopFile *desktop_file,
593                 const char     *section_name)
594 {
595   BusDesktopFileSection *section;
596   int i;
597   
598   if (section_name == NULL)
599     return NULL;
600   
601   for (i = 0; i < desktop_file->n_sections; i ++)
602     {
603       section = &desktop_file->sections[i];
604
605       if (strcmp (section->section_name, section_name) == 0)
606         return section;
607     }
608   
609   return NULL;
610 }
611
612 static BusDesktopFileLine *
613 lookup_line (BusDesktopFile        *desktop_file,
614              BusDesktopFileSection *section,
615              const char            *keyname)
616 {
617   BusDesktopFileLine *line;
618   int i;
619
620   for (i = 0; i < section->n_lines; i++)
621     {
622       line = &section->lines[i];
623       
624       if (strcmp (line->key, keyname) == 0)
625         return line;
626     }
627   
628   return NULL;
629 }
630
631 dbus_bool_t
632 bus_desktop_file_get_raw (BusDesktopFile  *desktop_file,
633                           const char      *section_name,
634                           const char      *keyname,
635                           const char     **val)
636 {
637   BusDesktopFileSection *section;
638   BusDesktopFileLine *line;
639
640   *val = NULL;
641
642   section = lookup_section (desktop_file, section_name);
643   
644   if (!section)
645     return FALSE;
646
647   line = lookup_line (desktop_file,
648                       section,
649                       keyname);
650
651   if (!line)
652     return FALSE;
653   
654   *val = line->value;
655   
656   return TRUE;
657 }
658
659 dbus_bool_t
660 bus_desktop_file_get_string (BusDesktopFile  *desktop_file,
661                              const char      *section,
662                              const char      *keyname,
663                              char           **val)
664 {
665   const char *raw;
666   
667   *val = NULL;
668   
669   if (!bus_desktop_file_get_raw (desktop_file, section, keyname, &raw))
670     return FALSE;
671
672   _DBUS_HANDLE_OOM (*val = _dbus_strdup (raw));
673   
674   return TRUE;
675 }