pidgin/pidgin

Make sure we remove all the files we install.
release-2.x.y
2021-04-11, Gary Kramlich
5407cbf3c5dd
Make sure we remove all the files we install.

Patch was provided by Johann Lochner.

Testing Done:
Build and ran the installer and uninstaller and verified that `C:\Program Files (x86)\Pidgin` was empty and therefore removed.

Bugs closed: PIDGIN-17515

Reviewed at https://reviews.imfreedom.org/r/612/
/**
* @file purple-desktop-item.c Functions for managing .desktop files
* @ingroup core
*/
/* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* 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 02111-1301 USA
*
*/
/*
* The following code has been adapted from gnome-desktop-item.[ch],
* as found on gnome-desktop-2.8.1.
*
* Copyright (C) 2004 by Alceste Scalas <alceste.scalas@gmx.net>.
*
* Original copyright notice:
*
* Copyright (C) 1999, 2000 Red Hat Inc.
* Copyright (C) 2001 Sid Vicious
* All rights reserved.
*
* This file is part of the Gnome Library.
*
* The Gnome Library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* The Gnome Library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with the Gnome Library; see the file COPYING.LIB. If not,
* write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02111-1301, USA.
*/
#include "internal.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "desktopitem.h"
#include "glibcompat.h"
struct _PurpleDesktopItem {
int refcount;
/* all languages used */
GList *languages;
PurpleDesktopItemType type;
/* `modified' means that the ditem has been
* modified since the last save. */
gboolean modified;
/* Keys of the main section only */
GList *keys;
GList *sections;
/* This includes ALL keys, including
* other sections, separated by '/' */
GHashTable *main_hash;
char *location;
time_t mtime;
};
typedef struct {
char *name;
GList *keys;
} Section;
typedef enum {
ENCODING_UNKNOWN,
ENCODING_UTF8,
ENCODING_LEGACY_MIXED
} Encoding;
/**************************************************************************
* Private utility functions
**************************************************************************/
static PurpleDesktopItemType
type_from_string (const char *type)
{
if (!type)
return PURPLE_DESKTOP_ITEM_TYPE_NULL;
switch (type [0]) {
case 'A':
if (purple_strequal (type, "Application"))
return PURPLE_DESKTOP_ITEM_TYPE_APPLICATION;
break;
case 'L':
if (purple_strequal (type, "Link"))
return PURPLE_DESKTOP_ITEM_TYPE_LINK;
break;
case 'F':
if (purple_strequal (type, "FSDevice"))
return PURPLE_DESKTOP_ITEM_TYPE_FSDEVICE;
break;
case 'M':
if (purple_strequal (type, "MimeType"))
return PURPLE_DESKTOP_ITEM_TYPE_MIME_TYPE;
break;
case 'D':
if (purple_strequal (type, "Directory"))
return PURPLE_DESKTOP_ITEM_TYPE_DIRECTORY;
break;
case 'S':
if (purple_strequal (type, "Service"))
return PURPLE_DESKTOP_ITEM_TYPE_SERVICE;
else if (purple_strequal (type, "ServiceType"))
return PURPLE_DESKTOP_ITEM_TYPE_SERVICE_TYPE;
break;
default:
break;
}
return PURPLE_DESKTOP_ITEM_TYPE_OTHER;
}
static Section *
find_section (PurpleDesktopItem *item, const char *section)
{
GList *li;
Section *sec;
if (section == NULL)
return NULL;
if (purple_strequal (section, "Desktop Entry"))
return NULL;
for (li = item->sections; li != NULL; li = li->next) {
sec = li->data;
if (purple_strequal (sec->name, section))
return sec;
}
sec = g_new0 (Section, 1);
sec->name = g_strdup (section);
sec->keys = NULL;
item->sections = g_list_append (item->sections, sec);
/* Don't mark the item modified, this is just an empty section,
* it won't be saved even */
return sec;
}
static Section *
section_from_key (PurpleDesktopItem *item, const char *key)
{
char *p;
char *name;
Section *sec;
if (key == NULL)
return NULL;
p = strchr (key, '/');
if (p == NULL)
return NULL;
name = g_strndup (key, p - key);
sec = find_section (item, name);
g_free (name);
return sec;
}
static const char *
key_basename (const char *key)
{
char *p = strrchr (key, '/');
if (p != NULL)
return p+1;
else
return key;
}
static void
set (PurpleDesktopItem *item, const char *key, const char *value)
{
Section *sec = section_from_key (item, key);
if (sec != NULL) {
if (value != NULL) {
if (g_hash_table_lookup (item->main_hash, key) == NULL)
sec->keys = g_list_append
(sec->keys,
g_strdup (key_basename (key)));
g_hash_table_replace (item->main_hash,
g_strdup (key),
g_strdup (value));
} else {
GList *list = g_list_find_custom
(sec->keys, key_basename (key),
(GCompareFunc)strcmp);
if (list != NULL) {
g_free (list->data);
sec->keys =
g_list_delete_link (sec->keys, list);
}
g_hash_table_remove (item->main_hash, key);
}
} else {
if (value != NULL) {
if (g_hash_table_lookup (item->main_hash, key) == NULL)
item->keys = g_list_append (item->keys,
g_strdup (key));
g_hash_table_replace (item->main_hash,
g_strdup (key),
g_strdup (value));
} else {
GList *list = g_list_find_custom
(item->keys, key, (GCompareFunc)strcmp);
if (list != NULL) {
g_free (list->data);
item->keys =
g_list_delete_link (item->keys, list);
}
g_hash_table_remove (item->main_hash, key);
}
}
item->modified = TRUE;
}
static void
_purple_desktop_item_set_string (PurpleDesktopItem *item,
const char *attr,
const char *value)
{
g_return_if_fail (item != NULL);
g_return_if_fail (item->refcount > 0);
g_return_if_fail (attr != NULL);
set (item, attr, value);
if (purple_strequal (attr, PURPLE_DESKTOP_ITEM_TYPE))
item->type = type_from_string (value);
}
static PurpleDesktopItem *
_purple_desktop_item_new (void)
{
PurpleDesktopItem *retval;
retval = g_new0 (PurpleDesktopItem, 1);
retval->refcount++;
retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_free);
/* These are guaranteed to be set */
_purple_desktop_item_set_string (retval,
PURPLE_DESKTOP_ITEM_NAME,
_("No name"));
_purple_desktop_item_set_string (retval,
PURPLE_DESKTOP_ITEM_ENCODING,
"UTF-8");
_purple_desktop_item_set_string (retval,
PURPLE_DESKTOP_ITEM_VERSION,
"1.0");
return retval;
}
static gpointer
_purple_desktop_item_copy (gpointer boxed)
{
return purple_desktop_item_copy (boxed);
}
static void
_purple_desktop_item_free (gpointer boxed)
{
purple_desktop_item_unref (boxed);
}
/* Note, does not include the trailing \n */
static char *
my_fgets (char *buf, gsize bufsize, FILE *df)
{
int c;
gsize pos;
g_return_val_if_fail (buf != NULL, NULL);
g_return_val_if_fail (df != NULL, NULL);
pos = 0;
buf[0] = '\0';
do {
c = getc (df);
if (c == EOF || c == '\n')
break;
buf[pos++] = c;
} while (pos < bufsize-1);
if (c == EOF && pos == 0)
return NULL;
buf[pos] = '\0';
return buf;
}
static Encoding
get_encoding (FILE *df)
{
gboolean old_kde = FALSE;
char buf [BUFSIZ];
gboolean all_valid_utf8 = TRUE;
while (my_fgets (buf, sizeof (buf), df) != NULL) {
if (strncmp (PURPLE_DESKTOP_ITEM_ENCODING,
buf,
strlen (PURPLE_DESKTOP_ITEM_ENCODING)) == 0) {
char *p = &buf[strlen (PURPLE_DESKTOP_ITEM_ENCODING)];
if (*p == ' ')
p++;
if (*p != '=')
continue;
p++;
if (*p == ' ')
p++;
if (purple_strequal (p, "UTF-8")) {
return ENCODING_UTF8;
} else if (purple_strequal (p, "Legacy-Mixed")) {
return ENCODING_LEGACY_MIXED;
} else {
/* According to the spec we're not supposed
* to read a file like this */
return ENCODING_UNKNOWN;
}
} else if (purple_strequal ("[KDE Desktop Entry]", buf)) {
old_kde = TRUE;
/* don't break yet, we still want to support
* Encoding even here */
}
if (all_valid_utf8 && ! g_utf8_validate (buf, -1, NULL))
all_valid_utf8 = FALSE;
}
if (old_kde)
return ENCODING_LEGACY_MIXED;
/* A dilemma, new KDE files are in UTF-8 but have no Encoding
* info, at this time we really can't tell. The best thing to
* do right now is to just assume UTF-8 if the whole file
* validates as utf8 I suppose */
if (all_valid_utf8)
return ENCODING_UTF8;
else
return ENCODING_LEGACY_MIXED;
}
static char *
snarf_locale_from_key (const char *key)
{
const char *brace;
char *locale, *p;
brace = strchr (key, '[');
if (brace == NULL)
return NULL;
locale = g_strdup (brace + 1);
if (*locale == '\0') {
g_free (locale);
return NULL;
}
p = strchr (locale, ']');
if (p == NULL) {
g_free (locale);
return NULL;
}
*p = '\0';
return locale;
}
static gboolean
check_locale (const char *locale)
{
GIConv cd = g_iconv_open ("UTF-8", locale);
if ((GIConv)-1 == cd)
return FALSE;
g_iconv_close (cd);
return TRUE;
}
static void
insert_locales (GHashTable *encodings, char *enc, ...)
{
va_list args;
char *s;
va_start (args, enc);
for (;;) {
s = va_arg (args, char *);
if (s == NULL)
break;
g_hash_table_insert (encodings, s, enc);
}
va_end (args);
}
/* make a standard conversion table from the desktop standard spec */
static GHashTable *
init_encodings (void)
{
GHashTable *encodings = g_hash_table_new (g_str_hash, g_str_equal);
/* "C" is plain ascii */
insert_locales (encodings, "ASCII", "C", NULL);
insert_locales (encodings, "ARMSCII-8", "by", NULL);
insert_locales (encodings, "BIG5", "zh_TW", NULL);
insert_locales (encodings, "CP1251", "be", "bg", NULL);
if (check_locale ("EUC-CN")) {
insert_locales (encodings, "EUC-CN", "zh_CN", NULL);
} else {
insert_locales (encodings, "GB2312", "zh_CN", NULL);
}
insert_locales (encodings, "EUC-JP", "ja", NULL);
insert_locales (encodings, "EUC-KR", "ko", NULL);
/*insert_locales (encodings, "GEORGIAN-ACADEMY", NULL);*/
insert_locales (encodings, "GEORGIAN-PS", "ka", NULL);
insert_locales (encodings, "ISO-8859-1", "br", "ca", "da", "de", "en", "es", "eu", "fi", "fr", "gl", "it", "nl", "wa", "no", "pt", "pt", "sv", NULL);
insert_locales (encodings, "ISO-8859-2", "cs", "hr", "hu", "pl", "ro", "sk", "sl", "sq", "sr", NULL);
insert_locales (encodings, "ISO-8859-3", "eo", NULL);
insert_locales (encodings, "ISO-8859-5", "mk", "sp", NULL);
insert_locales (encodings, "ISO-8859-7", "el", NULL);
insert_locales (encodings, "ISO-8859-9", "tr", NULL);
insert_locales (encodings, "ISO-8859-13", "lt", "lv", "mi", NULL);
insert_locales (encodings, "ISO-8859-14", "ga", "cy", NULL);
insert_locales (encodings, "ISO-8859-15", "et", NULL);
insert_locales (encodings, "KOI8-R", "ru", NULL);
insert_locales (encodings, "KOI8-U", "uk", NULL);
if (check_locale ("TCVN-5712")) {
insert_locales (encodings, "TCVN-5712", "vi", NULL);
} else {
insert_locales (encodings, "TCVN", "vi", NULL);
}
insert_locales (encodings, "TIS-620", "th", NULL);
/*insert_locales (encodings, "VISCII", NULL);*/
return encodings;
}
static const char *
get_encoding_from_locale (const char *locale)
{
char lang[3];
const char *encoding;
static GHashTable *encodings = NULL;
if (locale == NULL)
return NULL;
/* if locale includes encoding, use it */
encoding = strchr (locale, '.');
if (encoding != NULL) {
return encoding+1;
}
if (encodings == NULL)
encodings = init_encodings ();
/* first try the entire locale (at this point ll_CC) */
encoding = g_hash_table_lookup (encodings, locale);
if (encoding != NULL)
return encoding;
/* Try just the language */
strncpy (lang, locale, 2);
lang[2] = '\0';
return g_hash_table_lookup (encodings, lang);
}
static char *
decode_string_and_dup (const char *s)
{
char *p = g_malloc (strlen (s) + 1);
char *q = p;
do {
if (*s == '\\'){
switch (*(++s)){
case 's':
*p++ = ' ';
break;
case 't':
*p++ = '\t';
break;
case 'n':
*p++ = '\n';
break;
case '\\':
*p++ = '\\';
break;
case 'r':
*p++ = '\r';
break;
default:
*p++ = '\\';
*p++ = *s;
break;
}
} else {
*p++ = *s;
}
} while (*s++);
return q;
}
static char *
decode_string (const char *value, Encoding encoding, const char *locale)
{
char *retval = NULL;
/* if legacy mixed, then convert */
if (locale != NULL && encoding == ENCODING_LEGACY_MIXED) {
const char *char_encoding = get_encoding_from_locale (locale);
char *utf8_string;
if (char_encoding == NULL)
return NULL;
if (purple_strequal (char_encoding, "ASCII")) {
return decode_string_and_dup (value);
}
utf8_string = g_convert (value, -1, "UTF-8", char_encoding,
NULL, NULL, NULL);
if (utf8_string == NULL)
return NULL;
retval = decode_string_and_dup (utf8_string);
g_free (utf8_string);
return retval;
/* if utf8, then validate */
} else if (locale != NULL && encoding == ENCODING_UTF8) {
if ( ! g_utf8_validate (value, -1, NULL))
/* invalid utf8, ignore this key */
return NULL;
return decode_string_and_dup (value);
} else {
/* Meaning this is not a localized string */
return decode_string_and_dup (value);
}
}
/************************************************************
* Parser: *
************************************************************/
static gboolean G_GNUC_CONST
standard_is_boolean (const char * key)
{
static GHashTable *bools = NULL;
if (bools == NULL) {
bools = g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_insert (bools,
PURPLE_DESKTOP_ITEM_NO_DISPLAY,
PURPLE_DESKTOP_ITEM_NO_DISPLAY);
g_hash_table_insert (bools,
PURPLE_DESKTOP_ITEM_HIDDEN,
PURPLE_DESKTOP_ITEM_HIDDEN);
g_hash_table_insert (bools,
PURPLE_DESKTOP_ITEM_TERMINAL,
PURPLE_DESKTOP_ITEM_TERMINAL);
g_hash_table_insert (bools,
PURPLE_DESKTOP_ITEM_READ_ONLY,
PURPLE_DESKTOP_ITEM_READ_ONLY);
}
return g_hash_table_lookup (bools, key) != NULL;
}
static gboolean G_GNUC_CONST
standard_is_strings (const char *key)
{
static GHashTable *strings = NULL;
if (strings == NULL) {
strings = g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_insert (strings,
PURPLE_DESKTOP_ITEM_FILE_PATTERN,
PURPLE_DESKTOP_ITEM_FILE_PATTERN);
g_hash_table_insert (strings,
PURPLE_DESKTOP_ITEM_ACTIONS,
PURPLE_DESKTOP_ITEM_ACTIONS);
g_hash_table_insert (strings,
PURPLE_DESKTOP_ITEM_MIME_TYPE,
PURPLE_DESKTOP_ITEM_MIME_TYPE);
g_hash_table_insert (strings,
PURPLE_DESKTOP_ITEM_PATTERNS,
PURPLE_DESKTOP_ITEM_PATTERNS);
g_hash_table_insert (strings,
PURPLE_DESKTOP_ITEM_SORT_ORDER,
PURPLE_DESKTOP_ITEM_SORT_ORDER);
}
return g_hash_table_lookup (strings, key) != NULL;
}
/* If no need to cannonize, returns NULL */
static char *
cannonize (const char *key, const char *value)
{
if (standard_is_boolean (key)) {
if (value[0] == 'T' ||
value[0] == 't' ||
value[0] == 'Y' ||
value[0] == 'y' ||
atoi (value) != 0) {
return g_strdup ("true");
} else {
return g_strdup ("false");
}
} else if (standard_is_strings (key)) {
int len = strlen (value);
if (len == 0 || value[len-1] != ';') {
return g_strconcat (value, ";", NULL);
}
}
/* XXX: Perhaps we should canonize numeric values as well, but this
* has caused some subtle problems before so it needs to be done
* carefully if at all */
return NULL;
}
static void
insert_key (PurpleDesktopItem *item,
Section *cur_section,
Encoding encoding,
const char *key,
const char *value,
gboolean old_kde,
gboolean no_translations)
{
char *k;
char *val;
/* we always store everything in UTF-8 */
if (cur_section == NULL &&
purple_strequal (key, PURPLE_DESKTOP_ITEM_ENCODING)) {
k = g_strdup (key);
val = g_strdup ("UTF-8");
} else {
char *locale = snarf_locale_from_key (key);
/* If we're ignoring translations */
if (no_translations && locale != NULL) {
g_free (locale);
return;
}
val = decode_string (value, encoding, locale);
/* Ignore this key, it's whacked */
if (val == NULL) {
g_free (locale);
return;
}
g_strchomp (val);
/* For old KDE entries, we can also split by a comma
* on sort order, so convert to semicolons */
if (old_kde &&
cur_section == NULL &&
purple_strequal (key, PURPLE_DESKTOP_ITEM_SORT_ORDER) &&
strchr (val, ';') == NULL) {
int i;
for (i = 0; val[i] != '\0'; i++) {
if (val[i] == ',')
val[i] = ';';
}
}
/* Check some types, not perfect, but catches a lot
* of things */
if (cur_section == NULL) {
char *cannon = cannonize (key, val);
if (cannon != NULL) {
g_free (val);
val = cannon;
}
}
k = g_strdup (key);
/* Take care of the language part */
if (locale != NULL &&
purple_strequal (locale, "C")) {
char *p;
/* Whack C locale */
p = strchr (k, '[');
if(p) *p = '\0';
g_free (locale);
} else if (locale != NULL) {
char *p, *brace;
/* Whack the encoding part */
p = strchr (locale, '.');
if (p != NULL)
*p = '\0';
if (g_list_find_custom (item->languages, locale,
(GCompareFunc)strcmp) == NULL) {
item->languages = g_list_prepend
(item->languages, locale);
} else {
g_free (locale);
}
/* Whack encoding from encoding in the key */
brace = strchr (k, '[');
if(brace != NULL) {
p = strchr (brace, '.');
if (p != NULL) {
*p = ']';
*(p+1) = '\0';
}
}
}
}
if (cur_section == NULL) {
/* only add to list if we haven't seen it before */
if (g_hash_table_lookup (item->main_hash, k) == NULL) {
item->keys = g_list_prepend (item->keys,
g_strdup (k));
}
/* later duplicates override earlier ones */
g_hash_table_replace (item->main_hash, k, val);
} else {
char *full = g_strdup_printf
("%s/%s",
cur_section->name, k);
/* only add to list if we haven't seen it before */
if (g_hash_table_lookup (item->main_hash, full) == NULL) {
cur_section->keys =
g_list_prepend (cur_section->keys, k);
}
/* later duplicates override earlier ones */
g_hash_table_replace (item->main_hash,
full, val);
}
}
static const char *
lookup (const PurpleDesktopItem *item, const char *key)
{
return g_hash_table_lookup (item->main_hash, key);
}
static void
setup_type (PurpleDesktopItem *item, const char *uri)
{
const char *type = g_hash_table_lookup (item->main_hash,
PURPLE_DESKTOP_ITEM_TYPE);
if (type == NULL && uri != NULL) {
char *base = g_path_get_basename (uri);
if (purple_strequal(base, ".directory")) {
/* This gotta be a directory */
g_hash_table_replace (item->main_hash,
g_strdup (PURPLE_DESKTOP_ITEM_TYPE),
g_strdup ("Directory"));
item->keys = g_list_prepend
(item->keys, g_strdup (PURPLE_DESKTOP_ITEM_TYPE));
item->type = PURPLE_DESKTOP_ITEM_TYPE_DIRECTORY;
} else {
item->type = PURPLE_DESKTOP_ITEM_TYPE_NULL;
}
g_free (base);
} else {
item->type = type_from_string (type);
}
}
static const char *
lookup_locale (const PurpleDesktopItem *item, const char *key, const char *locale)
{
if (locale == NULL ||
purple_strequal (locale, "C")) {
return lookup (item, key);
} else {
const char *ret;
char *full = g_strdup_printf ("%s[%s]", key, locale);
ret = lookup (item, full);
g_free (full);
return ret;
}
}
/**
* Fallback to find something suitable for C locale.
*
* @return A newly allocated string which should be g_freed by the caller.
*/
static char *
try_english_key (PurpleDesktopItem *item, const char *key)
{
char *str = NULL;
char *locales[] = { "en_US", "en_GB", "en_AU", "en", NULL };
int i;
for (i = 0; locales[i] != NULL && str == NULL; i++) {
str = g_strdup (lookup_locale (item, key, locales[i]));
}
if (str != NULL) {
/* We need a 7-bit ascii string, so whack all
* above 127 chars */
guchar *p;
for (p = (guchar *)str; *p != '\0'; p++) {
if (*p > 127)
*p = '?';
}
}
return str;
}
static void
sanitize (PurpleDesktopItem *item, const char *uri)
{
const char *type;
type = lookup (item, PURPLE_DESKTOP_ITEM_TYPE);
/* understand old gnome style url exec thingies */
if (purple_strequal(type, "URL")) {
const char *exec = lookup (item, PURPLE_DESKTOP_ITEM_EXEC);
set (item, PURPLE_DESKTOP_ITEM_TYPE, "Link");
if (exec != NULL) {
/* Note, this must be in this order */
set (item, PURPLE_DESKTOP_ITEM_URL, exec);
set (item, PURPLE_DESKTOP_ITEM_EXEC, NULL);
}
}
/* we make sure we have Name, Encoding and Version */
if (lookup (item, PURPLE_DESKTOP_ITEM_NAME) == NULL) {
char *name = try_english_key (item, PURPLE_DESKTOP_ITEM_NAME);
/* If no name, use the basename */
if (name == NULL && uri != NULL)
name = g_path_get_basename (uri);
/* If no uri either, use same default as gnome_desktop_item_new */
if (name == NULL)
name = g_strdup (_("No name"));
g_hash_table_replace (item->main_hash,
g_strdup (PURPLE_DESKTOP_ITEM_NAME),
name);
item->keys = g_list_prepend
(item->keys, g_strdup (PURPLE_DESKTOP_ITEM_NAME));
}
if (lookup (item, PURPLE_DESKTOP_ITEM_ENCODING) == NULL) {
/* We store everything in UTF-8 so write that down */
g_hash_table_replace (item->main_hash,
g_strdup (PURPLE_DESKTOP_ITEM_ENCODING),
g_strdup ("UTF-8"));
item->keys = g_list_prepend
(item->keys, g_strdup (PURPLE_DESKTOP_ITEM_ENCODING));
}
if (lookup (item, PURPLE_DESKTOP_ITEM_VERSION) == NULL) {
/* this is the version that we follow, so write it down */
g_hash_table_replace (item->main_hash,
g_strdup (PURPLE_DESKTOP_ITEM_VERSION),
g_strdup ("1.0"));
item->keys = g_list_prepend
(item->keys, g_strdup (PURPLE_DESKTOP_ITEM_VERSION));
}
}
enum {
FirstBrace,
OnSecHeader,
IgnoreToEOL,
IgnoreToEOLFirst,
KeyDef,
KeyDefOnKey,
KeyValue
};
static PurpleDesktopItem *
ditem_load (FILE *df,
gboolean no_translations,
const char *uri)
{
int state;
char CharBuffer [1024];
char *next = CharBuffer;
int c;
Encoding encoding;
PurpleDesktopItem *item;
Section *cur_section = NULL;
char *key = NULL;
gboolean old_kde = FALSE;
encoding = get_encoding (df);
if (encoding == ENCODING_UNKNOWN) {
fclose(df);
/* spec says, don't read this file */
printf ("Unknown encoding of .desktop file");
return NULL;
}
/* Rewind since get_encoding goes through the file */
if (fseek(df, 0L, SEEK_SET)) {
fclose(df);
/* spec says, don't read this file */
printf ("fseek() error on .desktop file");
return NULL;
}
item = _purple_desktop_item_new ();
item->modified = FALSE;
/* Note: location and mtime are filled in by the new_from_file
* function since it has those values */
#define PURPLE_DESKTOP_ITEM_OVERFLOW (next == &CharBuffer [sizeof(CharBuffer)-1])
state = FirstBrace;
while ((c = getc (df)) != EOF) {
if (c == '\r') /* Ignore Carriage Return */
continue;
switch (state) {
case OnSecHeader:
if (c == ']' || PURPLE_DESKTOP_ITEM_OVERFLOW) {
*next = '\0';
next = CharBuffer;
/* keys were inserted in reverse */
if (cur_section != NULL &&
cur_section->keys != NULL) {
cur_section->keys = g_list_reverse
(cur_section->keys);
}
if (purple_strequal (CharBuffer, "KDE Desktop Entry")) {
/* Main section */
cur_section = NULL;
old_kde = TRUE;
} else if (purple_strequal(CharBuffer, "Desktop Entry")) {
/* Main section */
cur_section = NULL;
} else {
cur_section = g_new0 (Section, 1);
cur_section->name =
g_strdup (CharBuffer);
cur_section->keys = NULL;
item->sections = g_list_prepend
(item->sections, cur_section);
}
state = IgnoreToEOL;
} else if (c == '[') {
/* FIXME: probably error out instead of ignoring this */
} else {
*next++ = c;
}
break;
case IgnoreToEOL:
case IgnoreToEOLFirst:
if (c == '\n'){
if (state == IgnoreToEOLFirst)
state = FirstBrace;
else
state = KeyDef;
next = CharBuffer;
}
break;
case FirstBrace:
case KeyDef:
case KeyDefOnKey:
if (c == '#') {
if (state == FirstBrace)
state = IgnoreToEOLFirst;
else
state = IgnoreToEOL;
break;
}
if (c == '[' && state != KeyDefOnKey){
state = OnSecHeader;
next = CharBuffer;
g_free (key);
key = NULL;
break;
}
/* On first pass, don't allow dangling keys */
if (state == FirstBrace)
break;
if ((c == ' ' && state != KeyDefOnKey) || c == '\t')
break;
if (c == '\n' || PURPLE_DESKTOP_ITEM_OVERFLOW) { /* Abort Definition */
next = CharBuffer;
state = KeyDef;
break;
}
if (c == '=' || PURPLE_DESKTOP_ITEM_OVERFLOW){
*next = '\0';
g_free (key);
key = g_strdup (CharBuffer);
state = KeyValue;
next = CharBuffer;
} else {
*next++ = c;
state = KeyDefOnKey;
}
break;
case KeyValue:
if (PURPLE_DESKTOP_ITEM_OVERFLOW || c == '\n'){
*next = '\0';
insert_key (item, cur_section, encoding,
key, CharBuffer, old_kde,
no_translations);
g_free (key);
key = NULL;
state = (c == '\n') ? KeyDef : IgnoreToEOL;
next = CharBuffer;
} else {
*next++ = c;
}
break;
} /* switch */
} /* while ((c = getc_unlocked (f)) != EOF) */
if (c == EOF && state == KeyValue) {
*next = '\0';
insert_key (item, cur_section, encoding,
key, CharBuffer, old_kde,
no_translations);
g_free (key);
key = NULL;
}
#undef PURPLE_DESKTOP_ITEM_OVERFLOW
/* keys were inserted in reverse */
if (cur_section != NULL &&
cur_section->keys != NULL) {
cur_section->keys = g_list_reverse (cur_section->keys);
}
/* keys were inserted in reverse */
item->keys = g_list_reverse (item->keys);
/* sections were inserted in reverse */
item->sections = g_list_reverse (item->sections);
/* sanitize some things */
sanitize (item, uri);
/* make sure that we set up the type */
setup_type (item, uri);
fclose (df);
return item;
}
static void
copy_string_hash (gpointer key, gpointer value, gpointer user_data)
{
GHashTable *copy = user_data;
g_hash_table_replace (copy,
g_strdup (key),
g_strdup (value));
}
static void
free_section (Section *section)
{
g_free (section->name);
section->name = NULL;
g_list_free_full(section->keys, (GDestroyNotify)g_free);
section->keys = NULL;
g_free (section);
}
static Section *
dup_section (Section *sec)
{
GList *li;
Section *retval = g_new0 (Section, 1);
retval->name = g_strdup (sec->name);
retval->keys = g_list_copy (sec->keys);
for (li = retval->keys; li != NULL; li = li->next)
li->data = g_strdup (li->data);
return retval;
}
/**************************************************************************
* Public functions
**************************************************************************/
PurpleDesktopItem *
purple_desktop_item_new_from_file (const char *filename)
{
PurpleDesktopItem *retval;
FILE *dfile;
g_return_val_if_fail (filename != NULL, NULL);
dfile = g_fopen(filename, "r");
if (!dfile) {
printf ("Can't open %s: %s", filename, g_strerror(errno));
return NULL;
}
retval = ditem_load(dfile, FALSE, filename);
return retval;
}
PurpleDesktopItemType
purple_desktop_item_get_entry_type (const PurpleDesktopItem *item)
{
g_return_val_if_fail (item != NULL, 0);
g_return_val_if_fail (item->refcount > 0, 0);
return item->type;
}
const char *
purple_desktop_item_get_string (const PurpleDesktopItem *item,
const char *attr)
{
g_return_val_if_fail (item != NULL, NULL);
g_return_val_if_fail (item->refcount > 0, NULL);
g_return_val_if_fail (attr != NULL, NULL);
return lookup (item, attr);
}
PurpleDesktopItem *
purple_desktop_item_copy (const PurpleDesktopItem *item)
{
GList *li;
PurpleDesktopItem *retval;
g_return_val_if_fail (item != NULL, NULL);
g_return_val_if_fail (item->refcount > 0, NULL);
retval = _purple_desktop_item_new ();
retval->type = item->type;
retval->modified = item->modified;
retval->location = g_strdup (item->location);
retval->mtime = item->mtime;
/* Languages */
retval->languages = g_list_copy (item->languages);
for (li = retval->languages; li != NULL; li = li->next)
li->data = g_strdup (li->data);
/* Keys */
retval->keys = g_list_copy (item->keys);
for (li = retval->keys; li != NULL; li = li->next)
li->data = g_strdup (li->data);
/* Sections */
retval->sections = g_list_copy (item->sections);
for (li = retval->sections; li != NULL; li = li->next)
li->data = dup_section (li->data);
retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_free);
g_hash_table_foreach (item->main_hash,
copy_string_hash,
retval->main_hash);
return retval;
}
void
purple_desktop_item_unref (PurpleDesktopItem *item)
{
g_return_if_fail (item != NULL);
g_return_if_fail (item->refcount > 0);
item->refcount--;
if(item->refcount != 0)
return;
g_list_free_full(item->languages, (GDestroyNotify)g_free);
item->languages = NULL;
g_list_free_full(item->keys, (GDestroyNotify)g_free);
item->keys = NULL;
g_list_free_full(item->sections, (GDestroyNotify)free_section);
item->sections = NULL;
g_hash_table_destroy (item->main_hash);
item->main_hash = NULL;
g_free (item->location);
item->location = NULL;
g_free (item);
}
GType
purple_desktop_item_get_type (void)
{
static GType type = 0;
if (type == 0) {
type = g_boxed_type_register_static ("PurpleDesktopItem",
_purple_desktop_item_copy,
_purple_desktop_item_free);
}
return type;
}