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