diff options
Diffstat (limited to 'bus/desktop-file.c')
-rw-r--r-- | bus/desktop-file.c | 800 |
1 files changed, 800 insertions, 0 deletions
diff --git a/bus/desktop-file.c b/bus/desktop-file.c new file mode 100644 index 00000000..754a83c3 --- /dev/null +++ b/bus/desktop-file.c @@ -0,0 +1,800 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* desktop-file.c .desktop file parser + * + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2003 Red Hat Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <dbus/dbus-sysdeps.h> +#include <dbus/dbus-internals.h> +#include "desktop-file.h" +#include "utils.h" + +typedef struct +{ + char *key; + char *value; +} BusDesktopFileLine; + +typedef struct +{ + char *section_name; + + int n_lines; + BusDesktopFileLine *lines; + int n_allocated_lines; +} BusDesktopFileSection; + +struct BusDesktopFile +{ + int n_sections; + BusDesktopFileSection *sections; + int n_allocated_sections; +}; + +/** + * Parser for service files. + */ +typedef struct +{ + DBusString data; /**< The data from the file */ + + BusDesktopFile *desktop_file; /**< The resulting object */ + int current_section; /**< The current section being parsed */ + + int pos; /**< Current position */ + int len; /**< Length */ + int line_num; /**< Current line number */ + +} BusDesktopFileParser; + +#define VALID_KEY_CHAR 1 +#define VALID_LOCALE_CHAR 2 +static unsigned char valid[256] = { + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x3 , 0x2 , 0x0 , + 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , + 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x2 , + 0x0 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , + 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , +}; + +static void report_error (BusDesktopFileParser *parser, + char *message, + const char *error_name, + DBusError *error); + +static void +parser_free (BusDesktopFileParser *parser) +{ + bus_desktop_file_free (parser->desktop_file); + + _dbus_string_free (&parser->data); +} + +static void +bus_desktop_file_line_free (BusDesktopFileLine *line) +{ + dbus_free (line->key); + dbus_free (line->value); +} + +static void +bus_desktop_file_section_free (BusDesktopFileSection *section) +{ + int i; + + for (i = 0; i < section->n_lines; i++) + bus_desktop_file_line_free (§ion->lines[i]); + + dbus_free (section->lines); + dbus_free (section->section_name); +} + +void +bus_desktop_file_free (BusDesktopFile *desktop_file) +{ + int i; + + for (i = 0; i < desktop_file->n_sections; i++) + bus_desktop_file_section_free (&desktop_file->sections[i]); + dbus_free (desktop_file->sections); + + dbus_free (desktop_file); +} + +static dbus_bool_t +grow_lines_in_section (BusDesktopFileSection *section) +{ + BusDesktopFileLine *lines; + + int new_n_lines; + + if (section->n_allocated_lines == 0) + new_n_lines = 1; + else + new_n_lines = section->n_allocated_lines*2; + + lines = dbus_realloc (section->lines, + sizeof (BusDesktopFileLine) * new_n_lines); + + if (lines == NULL) + return FALSE; + + section->lines = lines; + section->n_allocated_lines = new_n_lines; + + return TRUE; +} + +static dbus_bool_t +grow_sections (BusDesktopFile *desktop_file) +{ + int new_n_sections; + BusDesktopFileSection *sections; + + if (desktop_file->n_allocated_sections == 0) + new_n_sections = 1; + else + new_n_sections = desktop_file->n_allocated_sections*2; + + sections = dbus_realloc (desktop_file->sections, + sizeof (BusDesktopFileSection) * new_n_sections); + if (sections == NULL) + return FALSE; + + desktop_file->sections = sections; + + desktop_file->n_allocated_sections = new_n_sections; + + return TRUE; +} + +static char * +unescape_string (BusDesktopFileParser *parser, + const DBusString *str, + int pos, + int end_pos, + DBusError *error) +{ + char *retval, *q; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* len + 1 is enough, because unescaping never makes the + * string longer + */ + retval = dbus_malloc (end_pos - pos + 1); + if (retval == NULL) + { + BUS_SET_OOM (error); + return NULL; + } + + q = retval; + + while (pos < end_pos) + { + if (_dbus_string_get_byte (str, pos) == 0) + { + /* Found an embedded null */ + dbus_free (retval); + report_error (parser, "Text to be unescaped contains embedded nul", + BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES, error); + return NULL; + } + + if (_dbus_string_get_byte (str, pos) == '\\') + { + pos ++; + + if (pos >= end_pos) + { + /* Escape at end of string */ + dbus_free (retval); + report_error (parser, "Text to be unescaped ended in \\", + BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES, error); + return NULL; + } + + switch (_dbus_string_get_byte (str, pos)) + { + case 's': + *q++ = ' '; + break; + case 't': + *q++ = '\t'; + break; + case 'n': + *q++ = '\n'; + break; + case 'r': + *q++ = '\r'; + break; + case '\\': + *q++ = '\\'; + break; + default: + /* Invalid escape code */ + dbus_free (retval); + report_error (parser, "Text to be unescaped had invalid escape sequence", + BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES, error); + return NULL; + } + pos++; + } + else + { + *q++ =_dbus_string_get_byte (str, pos); + + pos++; + } + } + + *q = 0; + + return retval; +} + +static BusDesktopFileSection* +new_section (BusDesktopFile *desktop_file, + const char *name) +{ + int n; + char *name_copy; + + if (desktop_file->n_allocated_sections == desktop_file->n_sections) + { + if (!grow_sections (desktop_file)) + return NULL; + } + + name_copy = _dbus_strdup (name); + if (name_copy == NULL) + return NULL; + + n = desktop_file->n_sections; + desktop_file->sections[n].section_name = name_copy; + + desktop_file->sections[n].n_lines = 0; + desktop_file->sections[n].lines = NULL; + desktop_file->sections[n].n_allocated_lines = 0; + + if (!grow_lines_in_section (&desktop_file->sections[n])) + { + dbus_free (desktop_file->sections[n].section_name); + desktop_file->sections[n].section_name = NULL; + return NULL; + } + + desktop_file->n_sections += 1; + + return &desktop_file->sections[n]; +} + +static BusDesktopFileSection* +open_section (BusDesktopFileParser *parser, + char *name) +{ + BusDesktopFileSection *section; + + section = new_section (parser->desktop_file, name); + if (section == NULL) + return NULL; + + parser->current_section = parser->desktop_file->n_sections - 1; + _dbus_assert (&parser->desktop_file->sections[parser->current_section] == section); + + return section; +} + +static BusDesktopFileLine * +new_line (BusDesktopFileParser *parser) +{ + BusDesktopFileSection *section; + BusDesktopFileLine *line; + + section = &parser->desktop_file->sections[parser->current_section]; + + if (section->n_allocated_lines == section->n_lines) + { + if (!grow_lines_in_section (section)) + return NULL; + } + + line = §ion->lines[section->n_lines++]; + + memset (line, 0, sizeof (BusDesktopFileLine)); + + return line; +} + +static dbus_bool_t +is_blank_line (BusDesktopFileParser *parser) +{ + int p; + char c; + + p = parser->pos; + + c = _dbus_string_get_byte (&parser->data, p); + + while (c && c != '\n') + { + if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f')) + return FALSE; + + p++; + c = _dbus_string_get_byte (&parser->data, p); + } + + return TRUE; +} + +static void +parse_comment_or_blank (BusDesktopFileParser *parser) +{ + int line_end, eol_len; + + if (!_dbus_string_find_eol (&parser->data, parser->pos, &line_end, &eol_len)) + line_end = parser->len; + + if (line_end == parser->len) + parser->pos = parser->len; + else + parser->pos = line_end + eol_len; + + parser->line_num += 1; +} + +static dbus_bool_t +is_valid_section_name (const char *name) +{ + /* 5. Group names may contain all ASCII characters except for control characters and '[' and ']'. */ + + while (*name) + { + if (!((*name >= 'A' && *name <= 'Z') || (*name >= 'a' || *name <= 'z') || + *name == '\n' || *name == '\t')) + return FALSE; + + name++; + } + + return TRUE; +} + +static dbus_bool_t +parse_section_start (BusDesktopFileParser *parser, DBusError *error) +{ + int line_end, eol_len; + char *section_name; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_find_eol (&parser->data, parser->pos, &line_end, &eol_len)) + line_end = parser->len; + + if (line_end - parser->pos <= 2 || + _dbus_string_get_byte (&parser->data, line_end - 1) != ']') + { + report_error (parser, "Invalid syntax for section header", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX, error); + parser_free (parser); + return FALSE; + } + + section_name = unescape_string (parser, + &parser->data, parser->pos + 1, line_end - 1, + error); + + if (section_name == NULL) + { + parser_free (parser); + return FALSE; + } + + if (!is_valid_section_name (section_name)) + { + report_error (parser, "Invalid characters in section name", BUS_DESKTOP_PARSE_ERROR_INVALID_CHARS, error); + parser_free (parser); + dbus_free (section_name); + return FALSE; + } + + if (open_section (parser, section_name) == NULL) + { + dbus_free (section_name); + parser_free (parser); + BUS_SET_OOM (error); + return FALSE; + } + + if (line_end == parser->len) + parser->pos = parser->len; + else + parser->pos = line_end + eol_len; + + parser->line_num += 1; + + dbus_free (section_name); + + return TRUE; +} + +static dbus_bool_t +parse_key_value (BusDesktopFileParser *parser, DBusError *error) +{ + int line_end, eol_len; + int key_start, key_end; + int value_start; + int p; + char *value, *tmp; + DBusString key; + BusDesktopFileLine *line; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_find_eol (&parser->data, parser->pos, &line_end, &eol_len)) + line_end = parser->len; + + p = parser->pos; + key_start = p; + while (p < line_end && + (valid[_dbus_string_get_byte (&parser->data, p)] & VALID_KEY_CHAR)) + p++; + key_end = p; + + if (key_start == key_end) + { + report_error (parser, "Empty key name", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX, error); + parser_free (parser); + return FALSE; + } + + /* We ignore locales for now */ + if (p < line_end && _dbus_string_get_byte (&parser->data, p) == '[') + { + if (line_end == parser->len) + parser->pos = parser->len; + else + parser->pos = line_end + eol_len; + + parser->line_num += 1; + + return TRUE; + } + + /* Skip space before '=' */ + while (p < line_end && _dbus_string_get_byte (&parser->data, p) == ' ') + p++; + + if (p < line_end && _dbus_string_get_byte (&parser->data, p) != '=') + { + report_error (parser, "Invalid characters in key name", BUS_DESKTOP_PARSE_ERROR_INVALID_CHARS, error); + parser_free (parser); + return FALSE; + } + + if (p == line_end) + { + report_error (parser, "No '=' in key/value pair", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX, error); + parser_free (parser); + return FALSE; + } + + /* Skip the '=' */ + p++; + + /* Skip space after '=' */ + while (p < line_end && _dbus_string_get_byte (&parser->data, p) == ' ') + p++; + + value_start = p; + + value = unescape_string (parser, &parser->data, value_start, line_end, error); + if (value == NULL) + { + parser_free (parser); + return FALSE; + } + + line = new_line (parser); + if (line == NULL) + { + dbus_free (value); + parser_free (parser); + BUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_string_init (&key)) + { + dbus_free (value); + parser_free (parser); + BUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_string_copy_len (&parser->data, key_start, key_end - key_start, + &key, 0)) + { + _dbus_string_free (&key); + dbus_free (value); + parser_free (parser); + BUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_string_steal_data (&key, &tmp)) + { + _dbus_string_free (&key); + dbus_free (value); + parser_free (parser); + BUS_SET_OOM (error); + return FALSE; + } + + _dbus_string_free (&key); + + line->key = tmp; + line->value = value; + + if (line_end == parser->len) + parser->pos = parser->len; + else + parser->pos = line_end + eol_len; + + parser->line_num += 1; + + return TRUE; +} + +static void +report_error (BusDesktopFileParser *parser, + char *message, + const char *error_name, + DBusError *error) +{ + const char *section_name = NULL; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (parser->current_section != -1) + section_name = parser->desktop_file->sections[parser->current_section].section_name; + + if (section_name) + dbus_set_error (error, error_name, + "Error in section %s at line %d: %s\n", section_name, parser->line_num, message); + else + dbus_set_error (error, error_name, + "Error at line %d: %s\n", parser->line_num, message); +} + +#if 0 +static void +dump_desktop_file (BusDesktopFile *file) +{ + int i; + + for (i = 0; i < file->n_sections; i++) + { + int j; + + printf ("[%s]\n", file->sections[i].section_name); + + for (j = 0; j < file->sections[i].n_lines; j++) + { + printf ("%s=%s\n", file->sections[i].lines[j].key, + file->sections[i].lines[j].value); + } + } +} +#endif + +BusDesktopFile* +bus_desktop_file_load (DBusString *filename, + DBusError *error) +{ + DBusString str; + BusDesktopFileParser parser; + DBusStat sb; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* Clearly there's a race here, but it's just to make it unlikely + * that we do something silly, we still handle doing it below. + */ + if (!_dbus_stat (filename, &sb, error)) + return NULL; + + if (sb.size > _DBUS_ONE_KILOBYTE * 128) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Desktop file size (%ld bytes) is too large", (long) sb.size); + return NULL; + } + + if (!_dbus_string_init (&str)) + { + BUS_SET_OOM (error); + return NULL; + } + + if (!_dbus_file_get_contents (&str, filename, error)) + { + _dbus_string_free (&str); + return NULL; + } + + if (!_dbus_string_validate_utf8 (&str, 0, _dbus_string_get_length (&str))) + { + _dbus_string_free (&str); + dbus_set_error (error, DBUS_ERROR_FAILED, + "invalid UTF-8"); + return NULL; + } + + parser.desktop_file = dbus_new0 (BusDesktopFile, 1); + if (parser.desktop_file == NULL) + { + _dbus_string_free (&str); + BUS_SET_OOM (error); + return NULL; + } + + parser.data = str; + parser.line_num = 1; + parser.pos = 0; + parser.len = _dbus_string_get_length (&parser.data); + parser.current_section = -1; + + while (parser.pos < parser.len) + { + if (_dbus_string_get_byte (&parser.data, parser.pos) == '[') + { + if (!parse_section_start (&parser, error)) + { + return NULL; + } + } + else if (is_blank_line (&parser) || + _dbus_string_get_byte (&parser.data, parser.pos) == '#') + parse_comment_or_blank (&parser); + else + { + if (!parse_key_value (&parser, error)) + { + return NULL; + } + } + } + + _dbus_string_free (&parser.data); + + return parser.desktop_file; +} + +static BusDesktopFileSection * +lookup_section (BusDesktopFile *desktop_file, + const char *section_name) +{ + BusDesktopFileSection *section; + int i; + + if (section_name == NULL) + return NULL; + + for (i = 0; i < desktop_file->n_sections; i ++) + { + section = &desktop_file->sections[i]; + + if (strcmp (section->section_name, section_name) == 0) + return section; + } + + return NULL; +} + +static BusDesktopFileLine * +lookup_line (BusDesktopFile *desktop_file, + BusDesktopFileSection *section, + const char *keyname) +{ + BusDesktopFileLine *line; + int i; + + for (i = 0; i < section->n_lines; i++) + { + line = §ion->lines[i]; + + if (strcmp (line->key, keyname) == 0) + return line; + } + + return NULL; +} + +dbus_bool_t +bus_desktop_file_get_raw (BusDesktopFile *desktop_file, + const char *section_name, + const char *keyname, + const char **val) +{ + BusDesktopFileSection *section; + BusDesktopFileLine *line; + + *val = NULL; + + section = lookup_section (desktop_file, section_name); + + if (!section) + return FALSE; + + line = lookup_line (desktop_file, + section, + keyname); + + if (!line) + return FALSE; + + *val = line->value; + + return TRUE; +} + +dbus_bool_t +bus_desktop_file_get_string (BusDesktopFile *desktop_file, + const char *section, + const char *keyname, + char **val, + DBusError *error) +{ + const char *raw; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + *val = NULL; + + if (!bus_desktop_file_get_raw (desktop_file, section, keyname, &raw)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "No \"%s\" key in .service file\n", keyname); + return FALSE; + } + + *val = _dbus_strdup (raw); + + if (*val == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; +} |