gnome-shell/src/shell-mobile-providers.c
2011-11-07 15:24:59 -05:00

817 lines
24 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
* Copyright (C) 2009 Novell, Inc.
* Author: Tambet Ingo (tambet@gmail.com).
*
* Copyright (C) 2009 - 2010 Red Hat, Inc.
*/
#include "config.h"
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <glib/gi18n.h>
#include "shell-mobile-providers.h"
#ifndef MOBILE_BROADBAND_PROVIDER_INFO
#define MOBILE_BROADBAND_PROVIDER_INFO DATADIR "/mobile-broadband-provider-info/serviceproviders.xml"
#endif
#define ISO_3166_COUNTRY_CODES DATADIR "/zoneinfo/iso3166.tab"
static GHashTable *
read_country_codes (void)
{
GHashTable *table;
GIOChannel *channel;
GString *buffer;
GError *error = NULL;
GIOStatus status;
channel = g_io_channel_new_file (ISO_3166_COUNTRY_CODES, "r", &error);
if (!channel) {
if (error) {
g_warning ("Could not read " ISO_3166_COUNTRY_CODES ": %s", error->message);
g_error_free (error);
} else
g_warning ("Could not read " ISO_3166_COUNTRY_CODES ": Unknown error");
return NULL;
}
table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
buffer = g_string_sized_new (32);
status = G_IO_STATUS_NORMAL;
while (status == G_IO_STATUS_NORMAL) {
status = g_io_channel_read_line_string (channel, buffer, NULL, &error);
switch (status) {
case G_IO_STATUS_NORMAL:
if (buffer->str[0] != '#') {
char **pieces;
pieces = g_strsplit (buffer->str, "\t", 2);
/* Hack for rh#556292; iso3166.tab is just wrong */
pieces[1] = pieces[1] ? g_strchomp (pieces[1]) : NULL;
if (pieces[1] && !strcmp (pieces[1], "Britain (UK)")) {
g_free (pieces[1]);
pieces[1] = g_strdup (_("United Kingdom"));
}
g_hash_table_insert (table, pieces[0], pieces[1]);
g_free (pieces);
}
g_string_truncate (buffer, 0);
break;
case G_IO_STATUS_EOF:
break;
case G_IO_STATUS_ERROR:
g_warning ("Error while reading: %s", error->message);
g_error_free (error);
break;
case G_IO_STATUS_AGAIN:
/* FIXME: Try again a few times, but really, it never happes, right? */
break;
}
}
g_string_free (buffer, TRUE);
g_io_channel_unref (channel);
return table;
}
/* XML Parser */
typedef enum {
PARSER_TOPLEVEL = 0,
PARSER_COUNTRY,
PARSER_PROVIDER,
PARSER_METHOD_GSM,
PARSER_METHOD_GSM_APN,
PARSER_METHOD_CDMA,
PARSER_ERROR
} MobileContextState;
typedef struct {
GHashTable *country_codes;
GHashTable *table;
char *current_country;
GSList *current_providers;
ShellMobileProvider *current_provider;
ShellMobileAccessMethod *current_method;
char *text_buffer;
MobileContextState state;
} MobileParser;
static ShellGsmMccMnc *
mcc_mnc_new (const char *mcc, const char *mnc)
{
ShellGsmMccMnc *m;
m = g_slice_new (ShellGsmMccMnc);
m->mcc = g_strstrip (g_strdup (mcc));
m->mnc = g_strstrip (g_strdup (mnc));
return m;
}
/* added in porting */
static ShellGsmMccMnc *
mcc_mnc_copy (const ShellGsmMccMnc *other) {
ShellGsmMccMnc *ret;
ret = g_slice_new (ShellGsmMccMnc);
ret->mcc = g_strdup (other->mcc);
ret->mnc = g_strdup (other->mnc);
return ret;
}
static void
mcc_mnc_free (ShellGsmMccMnc *m)
{
g_return_if_fail (m != NULL);
g_free (m->mcc);
g_free (m->mnc);
g_slice_free (ShellGsmMccMnc, m);
}
/* added in porting */
G_DEFINE_BOXED_TYPE (ShellGsmMccMnc, shell_gsm_mcc_mnc, mcc_mnc_copy, mcc_mnc_free)
static ShellMobileAccessMethod *
access_method_new (void)
{
ShellMobileAccessMethod *method;
method = g_slice_new0 (ShellMobileAccessMethod);
method->refs = 1;
method->lcl_names = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_free);
return method;
}
ShellMobileAccessMethod *
shell_mobile_access_method_ref (ShellMobileAccessMethod *method)
{
g_return_val_if_fail (method != NULL, NULL);
g_return_val_if_fail (method->refs > 0, NULL);
method->refs++;
return method;
}
void
shell_mobile_access_method_unref (ShellMobileAccessMethod *method)
{
g_return_if_fail (method != NULL);
g_return_if_fail (method->refs > 0);
if (--method->refs == 0) {
g_free (method->name);
g_hash_table_destroy (method->lcl_names);
g_free (method->username);
g_free (method->password);
g_free (method->gateway);
g_free (method->gsm_apn);
g_slist_foreach (method->dns, (GFunc) g_free, NULL);
g_slist_free (method->dns);
g_slice_free (ShellMobileAccessMethod, method);
}
}
GType
shell_mobile_access_method_get_type (void)
{
static GType type = 0;
if (G_UNLIKELY (type == 0)) {
type = g_boxed_type_register_static ("ShellMobileAccessMethod",
(GBoxedCopyFunc) shell_mobile_access_method_ref,
(GBoxedFreeFunc) shell_mobile_access_method_unref);
}
return type;
}
static ShellMobileProvider *
provider_new (void)
{
ShellMobileProvider *provider;
provider = g_slice_new0 (ShellMobileProvider);
provider->refs = 1;
provider->lcl_names = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_free);
return provider;
}
ShellMobileProvider *
shell_mobile_provider_ref (ShellMobileProvider *provider)
{
provider->refs++;
return provider;
}
void
shell_mobile_provider_unref (ShellMobileProvider *provider)
{
if (--provider->refs == 0) {
g_free (provider->name);
g_hash_table_destroy (provider->lcl_names);
g_slist_foreach (provider->methods, (GFunc) shell_mobile_access_method_unref, NULL);
g_slist_free (provider->methods);
g_slist_foreach (provider->gsm_mcc_mnc, (GFunc) mcc_mnc_free, NULL);
g_slist_free (provider->gsm_mcc_mnc);
g_slist_free (provider->cdma_sid);
g_slice_free (ShellMobileProvider, provider);
}
}
GType
shell_mobile_provider_get_type (void)
{
static GType type = 0;
if (G_UNLIKELY (type == 0)) {
type = g_boxed_type_register_static ("ShellMobileProvider",
(GBoxedCopyFunc) shell_mobile_provider_ref,
(GBoxedFreeFunc) shell_mobile_provider_unref);
}
return type;
}
static void
provider_list_free (gpointer data)
{
GSList *list = (GSList *) data;
while (list) {
shell_mobile_provider_unref ((ShellMobileProvider *) list->data);
list = g_slist_delete_link (list, list);
}
}
static void
parser_toplevel_start (MobileParser *parser,
const char *name,
const char **attribute_names,
const char **attribute_values)
{
int i;
if (!strcmp (name, "serviceproviders")) {
for (i = 0; attribute_names && attribute_names[i]; i++) {
if (!strcmp (attribute_names[i], "format")) {
if (strcmp (attribute_values[i], "2.0")) {
g_warning ("%s: mobile broadband provider database format '%s'"
" not supported.", __func__, attribute_values[i]);
parser->state = PARSER_ERROR;
break;
}
}
}
} else if (!strcmp (name, "country")) {
for (i = 0; attribute_names && attribute_names[i]; i++) {
if (!strcmp (attribute_names[i], "code")) {
char *country_code;
char *country;
country_code = g_ascii_strup (attribute_values[i], -1);
country = g_hash_table_lookup (parser->country_codes, country_code);
if (country) {
parser->current_country = g_strdup (country);
g_free (country_code);
} else
parser->current_country = country_code;
parser->state = PARSER_COUNTRY;
break;
}
}
}
}
static void
parser_country_start (MobileParser *parser,
const char *name,
const char **attribute_names,
const char **attribute_values)
{
if (!strcmp (name, "provider")) {
parser->state = PARSER_PROVIDER;
parser->current_provider = provider_new ();
}
}
static void
parser_provider_start (MobileParser *parser,
const char *name,
const char **attribute_names,
const char **attribute_values)
{
if (!strcmp (name, "gsm"))
parser->state = PARSER_METHOD_GSM;
else if (!strcmp (name, "cdma")) {
parser->state = PARSER_METHOD_CDMA;
parser->current_method = access_method_new ();
}
}
static void
parser_gsm_start (MobileParser *parser,
const char *name,
const char **attribute_names,
const char **attribute_values)
{
if (!strcmp (name, "network-id")) {
const char *mcc = NULL, *mnc = NULL;
int i;
for (i = 0; attribute_names && attribute_names[i]; i++) {
if (!strcmp (attribute_names[i], "mcc"))
mcc = attribute_values[i];
else if (!strcmp (attribute_names[i], "mnc"))
mnc = attribute_values[i];
if (mcc && strlen (mcc) && mnc && strlen (mnc)) {
parser->current_provider->gsm_mcc_mnc = g_slist_prepend (parser->current_provider->gsm_mcc_mnc,
mcc_mnc_new (mcc, mnc));
break;
}
}
} else if (!strcmp (name, "apn")) {
int i;
for (i = 0; attribute_names && attribute_names[i]; i++) {
if (!strcmp (attribute_names[i], "value")) {
parser->state = PARSER_METHOD_GSM_APN;
parser->current_method = access_method_new ();
parser->current_method->gsm_apn = g_strstrip (g_strdup (attribute_values[i]));
break;
}
}
}
}
static void
parser_cdma_start (MobileParser *parser,
const char *name,
const char **attribute_names,
const char **attribute_values)
{
if (!strcmp (name, "sid")) {
int i;
for (i = 0; attribute_names && attribute_names[i]; i++) {
if (!strcmp (attribute_names[i], "value")) {
unsigned long tmp;
errno = 0;
tmp = strtoul (attribute_values[i], NULL, 10);
if (errno == 0 && tmp > 0)
parser->current_provider->cdma_sid = g_slist_prepend (parser->current_provider->cdma_sid,
GUINT_TO_POINTER ((guint32) tmp));
break;
}
}
}
}
static void
mobile_parser_start_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer data,
GError **error)
{
MobileParser *parser = (MobileParser *) data;
if (parser->text_buffer) {
g_free (parser->text_buffer);
parser->text_buffer = NULL;
}
switch (parser->state) {
case PARSER_TOPLEVEL:
parser_toplevel_start (parser, element_name, attribute_names, attribute_values);
break;
case PARSER_COUNTRY:
parser_country_start (parser, element_name, attribute_names, attribute_values);
break;
case PARSER_PROVIDER:
parser_provider_start (parser, element_name, attribute_names, attribute_values);
break;
case PARSER_METHOD_GSM:
parser_gsm_start (parser, element_name, attribute_names, attribute_values);
break;
case PARSER_METHOD_CDMA:
parser_cdma_start (parser, element_name, attribute_names, attribute_values);
break;
default:
break;
}
}
static void
parser_country_end (MobileParser *parser,
const char *name)
{
if (!strcmp (name, "country")) {
g_hash_table_insert (parser->table, parser->current_country, parser->current_providers);
parser->current_country = NULL;
parser->current_providers = NULL;
parser->text_buffer = NULL;
parser->state = PARSER_TOPLEVEL;
}
}
static void
parser_provider_end (MobileParser *parser,
const char *name)
{
if (!strcmp (name, "name")) {
if (!parser->current_provider->name) {
/* Use the first one. */
parser->current_provider->name = parser->text_buffer;
parser->text_buffer = NULL;
}
} else if (!strcmp (name, "provider")) {
parser->current_provider->methods = g_slist_reverse (parser->current_provider->methods);
parser->current_provider->gsm_mcc_mnc = g_slist_reverse (parser->current_provider->gsm_mcc_mnc);
parser->current_provider->cdma_sid = g_slist_reverse (parser->current_provider->cdma_sid);
parser->current_providers = g_slist_prepend (parser->current_providers, parser->current_provider);
parser->current_provider = NULL;
parser->text_buffer = NULL;
parser->state = PARSER_COUNTRY;
}
}
static void
parser_gsm_end (MobileParser *parser,
const char *name)
{
if (!strcmp (name, "gsm")) {
parser->text_buffer = NULL;
parser->state = PARSER_PROVIDER;
}
}
static void
parser_gsm_apn_end (MobileParser *parser,
const char *name)
{
if (!strcmp (name, "name")) {
if (!parser->current_method->name) {
/* Use the first one. */
parser->current_method->name = parser->text_buffer;
parser->text_buffer = NULL;
}
} else if (!strcmp (name, "username")) {
parser->current_method->username = parser->text_buffer;
parser->text_buffer = NULL;
} else if (!strcmp (name, "password")) {
parser->current_method->password = parser->text_buffer;
parser->text_buffer = NULL;
} else if (!strcmp (name, "dns")) {
parser->current_method->dns = g_slist_prepend (parser->current_method->dns, parser->text_buffer);
parser->text_buffer = NULL;
} else if (!strcmp (name, "gateway")) {
parser->current_method->gateway = parser->text_buffer;
parser->text_buffer = NULL;
} else if (!strcmp (name, "apn")) {
parser->current_method->type = SHELL_MOBILE_ACCESS_METHOD_TYPE_GSM;
parser->current_method->dns = g_slist_reverse (parser->current_method->dns);
if (!parser->current_method->name)
parser->current_method->name = g_strdup (_("Default"));
parser->current_provider->methods = g_slist_prepend (parser->current_provider->methods,
parser->current_method);
parser->current_method = NULL;
parser->text_buffer = NULL;
parser->state = PARSER_METHOD_GSM;
}
}
static void
parser_cdma_end (MobileParser *parser,
const char *name)
{
if (!strcmp (name, "username")) {
parser->current_method->username = parser->text_buffer;
parser->text_buffer = NULL;
} else if (!strcmp (name, "password")) {
parser->current_method->password = parser->text_buffer;
parser->text_buffer = NULL;
} else if (!strcmp (name, "dns")) {
parser->current_method->dns = g_slist_prepend (parser->current_method->dns, parser->text_buffer);
parser->text_buffer = NULL;
} else if (!strcmp (name, "gateway")) {
parser->current_method->gateway = parser->text_buffer;
parser->text_buffer = NULL;
} else if (!strcmp (name, "cdma")) {
parser->current_method->type = SHELL_MOBILE_ACCESS_METHOD_TYPE_CDMA;
parser->current_method->dns = g_slist_reverse (parser->current_method->dns);
if (!parser->current_method->name)
parser->current_method->name = g_strdup (parser->current_provider->name);
parser->current_provider->methods = g_slist_prepend (parser->current_provider->methods,
parser->current_method);
parser->current_method = NULL;
parser->text_buffer = NULL;
parser->state = PARSER_PROVIDER;
}
}
static void
mobile_parser_end_element (GMarkupParseContext *context,
const gchar *element_name,
gpointer data,
GError **error)
{
MobileParser *parser = (MobileParser *) data;
switch (parser->state) {
case PARSER_COUNTRY:
parser_country_end (parser, element_name);
break;
case PARSER_PROVIDER:
parser_provider_end (parser, element_name);
break;
case PARSER_METHOD_GSM:
parser_gsm_end (parser, element_name);
break;
case PARSER_METHOD_GSM_APN:
parser_gsm_apn_end (parser, element_name);
break;
case PARSER_METHOD_CDMA:
parser_cdma_end (parser, element_name);
break;
default:
break;
}
}
static void
mobile_parser_characters (GMarkupParseContext *context,
const gchar *text,
gsize text_len,
gpointer data,
GError **error)
{
MobileParser *parser = (MobileParser *) data;
g_free (parser->text_buffer);
parser->text_buffer = g_strdup (text);
}
static const GMarkupParser mobile_parser = {
mobile_parser_start_element,
mobile_parser_end_element,
mobile_parser_characters,
NULL, /* passthrough */
NULL /* error */
};
/**
* shell_mobile_providers_parse:
* @out_ccs: (out) (allow-none): (element-type utf8 utf8): a #GHashTable containing
* country codes
*
* Returns: (element-type utf8 GList) (transfer container): a
* hash table where keys are country names #gchar, values are a #GSList
* of #ShellMobileProvider. Everything is destroyed with g_hash_table_destroy().
*/
GHashTable *
shell_mobile_providers_parse (GHashTable **out_ccs)
{
GMarkupParseContext *ctx;
GIOChannel *channel;
MobileParser parser;
GError *error = NULL;
char buffer[4096];
GIOStatus status;
gsize len = 0;
memset (&parser, 0, sizeof (MobileParser));
parser.country_codes = read_country_codes ();
if (!parser.country_codes)
goto out;
channel = g_io_channel_new_file (MOBILE_BROADBAND_PROVIDER_INFO, "r", &error);
if (!channel) {
if (error) {
g_warning ("Could not read " MOBILE_BROADBAND_PROVIDER_INFO ": %s", error->message);
g_error_free (error);
} else
g_warning ("Could not read " MOBILE_BROADBAND_PROVIDER_INFO ": Unknown error");
goto out;
}
parser.table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, provider_list_free);
parser.state = PARSER_TOPLEVEL;
ctx = g_markup_parse_context_new (&mobile_parser, 0, &parser, NULL);
status = G_IO_STATUS_NORMAL;
while (status == G_IO_STATUS_NORMAL) {
status = g_io_channel_read_chars (channel, buffer, sizeof (buffer), &len, &error);
switch (status) {
case G_IO_STATUS_NORMAL:
if (!g_markup_parse_context_parse (ctx, buffer, len, &error)) {
status = G_IO_STATUS_ERROR;
g_warning ("Error while parsing XML: %s", error->message);
g_error_free (error);;
}
break;
case G_IO_STATUS_EOF:
break;
case G_IO_STATUS_ERROR:
g_warning ("Error while reading: %s", error->message);
g_error_free (error);
break;
case G_IO_STATUS_AGAIN:
/* FIXME: Try again a few times, but really, it never happes, right? */
break;
}
}
g_io_channel_unref (channel);
g_markup_parse_context_free (ctx);
if (parser.current_provider) {
g_warning ("pending current provider");
shell_mobile_provider_unref (parser.current_provider);
}
if (parser.current_providers) {
g_warning ("pending current providers");
provider_list_free (parser.current_providers);
}
g_free (parser.current_country);
g_free (parser.text_buffer);
out:
if (parser.country_codes) {
if (out_ccs)
*out_ccs = parser.country_codes;
else
g_hash_table_destroy (parser.country_codes);
}
return parser.table;
}
static void
dump_generic (ShellMobileAccessMethod *method)
{
GSList *iter;
GString *dns;
g_print (" username: %s\n", method->username ? method->username : "");
g_print (" password: %s\n", method->password ? method->password : "");
dns = g_string_new (NULL);
for (iter = method->dns; iter; iter = g_slist_next (iter))
g_string_append_printf (dns, "%s%s", dns->len ? ", " : "", (char *) iter->data);
g_print (" dns : %s\n", dns->str);
g_string_free (dns, TRUE);
g_print (" gateway : %s\n", method->gateway ? method->gateway : "");
}
static void
dump_cdma (ShellMobileAccessMethod *method)
{
g_print (" CDMA: %s\n", method->name);
dump_generic (method);
}
static void
dump_gsm (ShellMobileAccessMethod *method)
{
g_print (" APN: %s (%s)\n", method->name, method->gsm_apn);
dump_generic (method);
}
static void
dump_country (gpointer key, gpointer value, gpointer user_data)
{
GSList *citer, *miter;
for (citer = value; citer; citer = g_slist_next (citer)) {
ShellMobileProvider *provider = citer->data;
g_print ("Provider: %s (%s)\n", provider->name, (const char *) key);
for (miter = provider->methods; miter; miter = g_slist_next (miter)) {
ShellMobileAccessMethod *method = miter->data;
GSList *liter;
for (liter = provider->gsm_mcc_mnc; liter; liter = g_slist_next (liter)) {
ShellGsmMccMnc *m = liter->data;
g_print (" MCC/MNC: %s-%s\n", m->mcc, m->mnc);
}
for (liter = provider->cdma_sid; liter; liter = g_slist_next (liter))
g_print (" SID: %d\n", GPOINTER_TO_UINT (liter->data));
switch (method->type) {
case SHELL_MOBILE_ACCESS_METHOD_TYPE_CDMA:
dump_cdma (method);
break;
case SHELL_MOBILE_ACCESS_METHOD_TYPE_GSM:
dump_gsm (method);
break;
default:
break;
}
g_print ("\n");
}
}
}
void
shell_mobile_providers_dump (GHashTable *providers)
{
g_return_if_fail (providers != NULL);
g_hash_table_foreach (providers, dump_country, NULL);
}
/* All the following don't exist in nm-applet, because C doesn't need
those. They're only needed for the introspection annotations
*/
/**
* shell_mobile_provider_get_gsm_mcc_mnc:
* @provider: a #ShellMobileProvider
*
* Returns: (element-type Shell.GsmMccMnc) (transfer none): the
* list of #ShellGsmMccMnc this provider exposes
*/
GSList *
shell_mobile_provider_get_gsm_mcc_mnc (ShellMobileProvider *provider)
{
return provider->gsm_mcc_mnc;
}
/**
* shell_mobile_provider_get_cdma_sid:
* @provider: a #ShellMobileProvider
*
* Returns: (element-type guint32) (transfer none): the
* list of CDMA sids this provider exposes
*/
GSList *
shell_mobile_provider_get_cdma_sid (ShellMobileProvider *provider)
{
return provider->cdma_sid;
}