/* -*- 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 <config.h> #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++]; _DBUS_ZERO(*line); 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; }