2003-03-12 Havoc Pennington <hp@redhat.com>
[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 dbus_bool_t
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   lines = dbus_realloc (section->lines,
141                         sizeof (BusDesktopFileLine) * new_n_lines);
142
143   if (lines == NULL)
144     return FALSE;
145   
146   section->lines = lines;
147   section->n_allocated_lines = new_n_lines;
148
149   return TRUE;
150 }
151
152 static dbus_bool_t
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   sections = dbus_realloc (desktop_file->sections,
164                            sizeof (BusDesktopFileSection) * new_n_sections);
165   if (sections == NULL)
166     return FALSE;
167   
168   desktop_file->sections = sections;
169   
170   desktop_file->n_allocated_sections = new_n_sections;
171
172   return TRUE;
173 }
174
175 static char *
176 unescape_string (BusDesktopFileParser *parser,
177                  const DBusString     *str,
178                  int                   pos,
179                  int                   end_pos,
180                  DBusError            *error)
181 {
182   char *retval, *q;
183   
184   /* len + 1 is enough, because unescaping never makes the
185    * string longer
186    */
187   retval = dbus_malloc (end_pos - pos + 1);
188   if (retval == NULL)
189     {
190       BUS_SET_OOM (error);
191       return NULL;
192     }
193
194   q = retval;
195   
196   while (pos < end_pos)
197     {
198       if (_dbus_string_get_byte (str, pos) == 0)
199         {
200           /* Found an embedded null */
201           dbus_free (retval);
202           report_error (parser, "Text to be unescaped contains embedded nul",
203                         BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES, error);
204           return NULL;
205         }
206
207       if (_dbus_string_get_byte (str, pos) == '\\')
208         {
209           pos ++;
210
211           if (pos >= end_pos)
212             {
213               /* Escape at end of string */
214               dbus_free (retval);
215               report_error (parser, "Text to be unescaped ended in \\",
216                             BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES, error);
217               return NULL;
218             }
219
220           switch (_dbus_string_get_byte (str, pos))
221             {
222             case 's':
223               *q++ = ' ';
224               break;
225            case 't':
226               *q++ = '\t';
227               break;
228            case 'n':
229               *q++ = '\n';
230               break;
231            case 'r':
232               *q++ = '\r';
233               break;
234            case '\\':
235               *q++ = '\\';
236               break;
237            default:
238              /* Invalid escape code */
239              dbus_free (retval);
240              report_error (parser, "Text to be unescaped had invalid escape sequence",
241                            BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES, error);
242              return NULL;
243             }
244           pos++;
245         }
246       else
247         {
248           *q++ =_dbus_string_get_byte (str, pos);
249
250           pos++;
251         }
252     }
253
254   *q = 0;
255
256   return retval;
257 }
258
259 static BusDesktopFileSection* 
260 new_section (BusDesktopFile *desktop_file,
261              const char     *name)
262 {
263   int n;
264   char *name_copy;
265   
266   if (desktop_file->n_allocated_sections == desktop_file->n_sections)
267     {
268       if (!grow_sections (desktop_file))
269         return NULL;
270     }
271
272   name_copy = _dbus_strdup (name);
273   if (name_copy == NULL)
274     return NULL;
275   
276   n = desktop_file->n_sections + 1;
277   desktop_file->sections[n].section_name = name_copy;
278
279   desktop_file->sections[n].n_lines = 0;
280   desktop_file->sections[n].lines = NULL;
281   desktop_file->sections[n].n_allocated_lines = 0;
282
283   if (!grow_lines_in_section (&desktop_file->sections[n]))
284     {
285       dbus_free (desktop_file->sections[n].section_name);
286       desktop_file->sections[n].section_name = NULL;
287       return NULL;
288     }
289
290   desktop_file->n_sections = n;
291   
292   return &desktop_file->sections[n];  
293 }
294
295 static BusDesktopFileSection* 
296 open_section (BusDesktopFileParser *parser,
297               char                 *name)
298 {  
299   BusDesktopFileSection *section;
300
301   section = new_section (parser->desktop_file, name);
302   if (section == NULL)
303     return NULL;
304   
305   parser->current_section = parser->desktop_file->n_sections - 1;
306   _dbus_assert (&parser->desktop_file->sections[parser->current_section] == section);
307   
308   return section;
309 }
310
311 static BusDesktopFileLine *
312 new_line (BusDesktopFileParser *parser)
313 {
314   BusDesktopFileSection *section;
315   BusDesktopFileLine *line;
316   
317   section = &parser->desktop_file->sections[parser->current_section];
318
319   if (section->n_allocated_lines == section->n_lines)
320     {
321       if (!grow_lines_in_section (section))
322         return NULL;
323     }
324
325   line = &section->lines[section->n_lines++];
326
327   memset (line, 0, sizeof (BusDesktopFileLine));
328     
329   return line;
330 }
331
332 static dbus_bool_t
333 is_blank_line (BusDesktopFileParser *parser)
334 {
335   int p;
336   char c;
337   
338   p = parser->pos;
339
340   c = _dbus_string_get_byte (&parser->data, p);
341
342   while (c && c != '\n')
343     {
344       if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f'))
345         return FALSE;
346       
347       p++;
348       c = _dbus_string_get_byte (&parser->data, p);
349     }
350
351   return TRUE;
352 }
353
354 static void
355 parse_comment_or_blank (BusDesktopFileParser *parser)
356 {
357   int line_end;
358   
359   if (!_dbus_string_find (&parser->data, parser->pos, "\n", &line_end))
360     line_end = parser->len;
361
362   if (line_end == parser->len)
363     parser->pos = parser->len;
364   else
365     parser->pos = line_end + 1;
366   
367   parser->line_num += 1;
368 }
369
370 static dbus_bool_t
371 is_valid_section_name (const char *name)
372 {
373   /* 5. Group names may contain all ASCII characters except for control characters and '[' and ']'. */
374
375   while (*name)
376     {
377       if (!((*name >= 'A' && *name <= 'Z') || (*name >= 'a' || *name <= 'z') ||
378             *name == '\n' || *name == '\t'))
379         return FALSE;
380       
381       name++;
382     }
383
384   return TRUE;
385 }
386
387 static dbus_bool_t
388 parse_section_start (BusDesktopFileParser *parser, DBusError *error)
389 {
390   int line_end;
391   char *section_name;
392   
393   if (!_dbus_string_find (&parser->data, parser->pos, "\n", &line_end))
394     line_end = parser->len;
395   
396   if (line_end - parser->pos <= 2 ||
397       _dbus_string_get_byte (&parser->data, line_end - 1) != ']')
398     {
399       report_error (parser, "Invalid syntax for section header", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX, error);
400       parser_free (parser);
401       return FALSE;
402     }
403
404   section_name = unescape_string (parser,
405                                   &parser->data, parser->pos + 1, line_end - 1,
406                                   error);
407
408   if (section_name == NULL)
409     {
410       parser_free (parser);
411       return FALSE;
412     }
413
414   if (!is_valid_section_name (section_name))
415     {
416       report_error (parser, "Invalid characters in section name", BUS_DESKTOP_PARSE_ERROR_INVALID_CHARS, error);
417       parser_free (parser);
418       dbus_free (section_name);
419       return FALSE;
420     }
421
422   if (open_section (parser, section_name) == NULL)
423     {
424       dbus_free (section_name);
425       return FALSE;
426     }
427
428   if (line_end == parser->len)
429     parser->pos = parser->len;
430   else
431     parser->pos = line_end + 1;
432   
433   parser->line_num += 1;
434
435   dbus_free (section_name);
436   
437   return TRUE;
438 }
439
440 static dbus_bool_t
441 parse_key_value (BusDesktopFileParser *parser, DBusError *error)
442 {
443   int line_end;
444   int key_start, key_end;
445   int value_start;
446   int p;
447   char *value, *tmp;
448   DBusString key;
449   BusDesktopFileLine *line;
450   
451   if (!_dbus_string_find (&parser->data, parser->pos, "\n", &line_end))
452     line_end = parser->len;
453
454   p = parser->pos;
455   key_start = p;
456   while (p < line_end &&
457          (valid[_dbus_string_get_byte (&parser->data, p)] & VALID_KEY_CHAR))
458     p++;
459   key_end = p;
460   
461   if (key_start == key_end)
462     {
463       report_error (parser, "Empty key name", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX, error);
464       parser_free (parser);
465       return FALSE;
466     }
467
468   /* We ignore locales for now */
469   
470   /* Skip space before '=' */
471   while (p < line_end && _dbus_string_get_byte (&parser->data, p) == ' ')
472     p++;
473
474   if (p < line_end && _dbus_string_get_byte (&parser->data, p) != '=')
475     {
476       report_error (parser, "Invalid characters in key name", BUS_DESKTOP_PARSE_ERROR_INVALID_CHARS, error);
477       parser_free (parser);
478       return FALSE;
479     }
480
481   if (p == line_end)
482     {
483       report_error (parser, "No '=' in key/value pair", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX, error);
484       parser_free (parser);
485       return FALSE;
486     }
487
488   /* Skip the '=' */
489   p++;
490
491   /* Skip space after '=' */
492   while (p < line_end && _dbus_string_get_byte (&parser->data, p) == ' ')
493     p++;
494
495   value_start = p;
496   
497   value = unescape_string (parser, &parser->data, value_start, line_end, error);
498   if (value == NULL)
499     {
500       parser_free (parser);
501       return FALSE;
502     }
503
504   line = new_line (parser);
505   if (line == NULL)
506     {
507       parser_free (parser);
508       return FALSE;
509     }
510   
511   if (!_dbus_string_init (&key, key_end - key_start))
512     {
513       parser_free (parser);
514       return FALSE;
515     }
516   
517   if (!_dbus_string_copy_len (&parser->data, key_start, key_end - key_start,
518                               &key, 0))
519     {
520       parser_free (parser);
521       return FALSE;
522     }
523   
524   if (!_dbus_string_steal_data (&key, &tmp))
525     {
526       parser_free (parser);
527       return FALSE;
528     }
529   
530   _dbus_string_free (&key);
531   
532   line->key = tmp;
533   line->value = value;
534
535   if (line_end == parser->len)
536     parser->pos = parser->len;
537   else
538     parser->pos = line_end + 1;
539   
540   parser->line_num += 1;
541
542   return TRUE;
543 }
544
545 static void
546 report_error (BusDesktopFileParser *parser,
547               char                 *message,
548               const char           *error_name,
549               DBusError            *error)
550 {
551   const char *section_name = NULL;
552     
553   if (parser->current_section != -1)
554     section_name = parser->desktop_file->sections[parser->current_section].section_name;
555
556   if (section_name)
557     dbus_set_error (error, error_name,
558                     "Error in section %s at line %d: %s\n", section_name, parser->line_num, message);
559   else
560     dbus_set_error (error, error_name,
561                     "Error at line %d: %s\n", parser->line_num, message);
562 }
563
564 #if 0
565 static void
566 dump_desktop_file (BusDesktopFile *file)
567 {
568   int i;
569
570   for (i = 0; i < file->n_sections; i++)
571     {
572       int j;
573       
574       printf ("[%s]\n", file->sections[i].section_name);
575
576       for (j = 0; j < file->sections[i].n_lines; j++)
577         {
578           printf ("%s=%s\n", file->sections[i].lines[j].key,
579                   file->sections[i].lines[j].value);
580         }
581     }
582 }
583 #endif
584
585 BusDesktopFile*
586 bus_desktop_file_load (DBusString *filename,
587                        DBusError  *error)
588 {
589   DBusString str;
590   BusDesktopFileParser parser;
591   DBusStat sb;
592   
593   /* Clearly there's a race here, but it's just to make it unlikely
594    * that we do something silly, we still handle doing it below.
595    */
596   if (!_dbus_stat (filename, &sb, error))
597     return NULL;
598
599   if (sb.size > _DBUS_ONE_KILOBYTE * 128)
600     {
601       dbus_set_error (error, DBUS_ERROR_FAILED,
602                       "Desktop file size (%ld bytes) is too large", (long) sb.size);
603       return NULL;
604     }
605   
606   if (!_dbus_string_init (&str, _DBUS_INT_MAX))
607     return NULL;
608   
609   if (!_dbus_file_get_contents (&str, filename, error))
610     {
611       _dbus_string_free (&str);
612       return NULL;
613     }
614
615   if (!_dbus_string_validate_utf8 (&str, 0, _dbus_string_get_length (&str)))
616     {
617       _dbus_string_free (&str);
618       dbus_set_error (error, DBUS_ERROR_FAILED,
619                       "invalid UTF-8");   
620       return NULL;
621     }
622   
623   parser.desktop_file = dbus_new0 (BusDesktopFile, 1);
624   if (parser.desktop_file == NULL)
625     {
626       _dbus_string_free (&str);
627       BUS_SET_OOM (error);
628       return NULL;
629     }
630   
631   parser.data = str;
632   parser.line_num = 1;
633   parser.pos = 0;
634   parser.len = _dbus_string_get_length (&parser.data);
635   parser.current_section = -1;
636   
637   while (parser.pos < parser.len)
638     {
639       if (_dbus_string_get_byte (&parser.data, parser.pos) == '[')
640         {
641           if (!parse_section_start (&parser, error))
642             {
643               _dbus_string_free (&parser.data);
644               return NULL;
645             }
646         }
647       else if (is_blank_line (&parser) ||
648                _dbus_string_get_byte (&parser.data, parser.pos) == '#')
649         parse_comment_or_blank (&parser);
650       else
651         {
652           if (!parse_key_value (&parser, error))
653             {
654               _dbus_string_free (&parser.data);
655               return NULL;
656             }
657         }
658     }
659
660   _dbus_string_free (&parser.data);
661
662   return parser.desktop_file;
663 }
664
665 static BusDesktopFileSection *
666 lookup_section (BusDesktopFile *desktop_file,
667                 const char     *section_name)
668 {
669   BusDesktopFileSection *section;
670   int i;
671   
672   if (section_name == NULL)
673     return NULL;
674   
675   for (i = 0; i < desktop_file->n_sections; i ++)
676     {
677       section = &desktop_file->sections[i];
678
679       if (strcmp (section->section_name, section_name) == 0)
680         return section;
681     }
682   
683   return NULL;
684 }
685
686 static BusDesktopFileLine *
687 lookup_line (BusDesktopFile        *desktop_file,
688              BusDesktopFileSection *section,
689              const char            *keyname)
690 {
691   BusDesktopFileLine *line;
692   int i;
693
694   for (i = 0; i < section->n_lines; i++)
695     {
696       line = &section->lines[i];
697       
698       if (strcmp (line->key, keyname) == 0)
699         return line;
700     }
701   
702   return NULL;
703 }
704
705 dbus_bool_t
706 bus_desktop_file_get_raw (BusDesktopFile  *desktop_file,
707                           const char      *section_name,
708                           const char      *keyname,
709                           const char     **val)
710 {
711   BusDesktopFileSection *section;
712   BusDesktopFileLine *line;
713
714   *val = NULL;
715
716   section = lookup_section (desktop_file, section_name);
717   
718   if (!section)
719     return FALSE;
720
721   line = lookup_line (desktop_file,
722                       section,
723                       keyname);
724
725   if (!line)
726     return FALSE;
727   
728   *val = line->value;
729   
730   return TRUE;
731 }
732
733 dbus_bool_t
734 bus_desktop_file_get_string (BusDesktopFile  *desktop_file,
735                              const char      *section,
736                              const char      *keyname,
737                              char           **val)
738 {
739   const char *raw;
740   
741   *val = NULL;
742   
743   if (!bus_desktop_file_get_raw (desktop_file, section, keyname, &raw))
744     return FALSE;
745
746   *val = _dbus_strdup (raw);
747
748   /* FIXME we don't distinguish "key not found" from "out of memory" here,
749    * which is broken.
750    */
751   if (*val == NULL)
752     return FALSE;
753   
754   return TRUE;
755 }