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