/* telepathy-gruschler - A Telepathy connection manager for social networks. * Copyright (C) 2009 Mathias Hasselmann * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include "facebook-connection.h" #include "facebook-contact-list.h" #include #include #include #include #include #include #include #include #include #include #include #include #include enum { PROP_0, PROP_EMAIL, PROP_PASSWORD, }; static const char *const _fixed_properties[] = { TP_IFACE_CHANNEL ".ChannelType", TP_IFACE_CHANNEL ".TargetHandleType", NULL }; static const char *const _allowed_properties[] = { TP_IFACE_CHANNEL ".TargetHandle", TP_IFACE_CHANNEL ".TargetID", NULL }; static const TpPresenceStatusOptionalArgumentSpec _presence_status_arguments[] = { { "message", "s", NULL, NULL }, { NULL, NULL, NULL, NULL } }; static const TpPresenceStatusSpec _presence_statuses[] = { { "unknown", TP_CONNECTION_PRESENCE_TYPE_UNKNOWN, FALSE, _presence_status_arguments, NULL, NULL }, { "offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE, _presence_status_arguments, NULL, NULL }, { "error", TP_CONNECTION_PRESENCE_TYPE_ERROR, FALSE, _presence_status_arguments, NULL, NULL }, { "idle", TP_CONNECTION_PRESENCE_TYPE_AWAY, FALSE, _presence_status_arguments, NULL, NULL }, { "active", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE, _presence_status_arguments, NULL, NULL }, { NULL, 0, FALSE, NULL, NULL, NULL } }; struct _GruschlerFacebookConnectionPrivate { RestProxy *facebook; SoupSession *session; GPid login_helper_pid; unsigned login_helper_id; GInputStream * login_helper_stream; TpHandleRepoIface *contacts; TpHandleRepoIface *groups; TpHandleRepoIface *lists; GHashTable *group_channels; GHashTable *list_channels; GHashTable *profiles; char *email; char *password; char *token; }; static void _channel_manager_iface_init (TpChannelManagerIface *iface); static void _aliasing_iface_init (TpSvcConnectionInterfaceAliasingClass *iface); G_DEFINE_TYPE_WITH_CODE (GruschlerFacebookConnection, gruschler_facebook_connection, TP_TYPE_BASE_CONNECTION, G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER, _channel_manager_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, tp_dbus_properties_mixin_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING, _aliasing_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS, tp_contacts_mixin_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE, tp_presence_mixin_simple_presence_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE, tp_presence_mixin_iface_init); ); static void _create_handle_repos (TpBaseConnection *connection, TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES]) { static const char *list_handle_names[] = { "publish", /* GRUSCHLER_FACEBOOK_LIST_HANDLE_PUBLISH */ "subscribe", /* GRUSCHLER_FACEBOOK_LIST_HANDLE_SUBSCRIBE */ "stored", /* GRUSCHLER_FACEBOOK_LIST_HANDLE_STORED */ "deny", /* GRUSCHLER_FACEBOOK_LIST_HANDLE_DENY */ NULL }; GruschlerFacebookConnection *self; self = GRUSCHLER_FACEBOOK_CONNECTION (connection); self->priv->contacts = tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_CONTACT, NULL, NULL); self->priv->groups = tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_GROUP, NULL, NULL); self->priv->lists = tp_static_handle_repo_new (TP_HANDLE_TYPE_LIST, list_handle_names); repos[TP_HANDLE_TYPE_CONTACT] = self->priv->contacts; repos[TP_HANDLE_TYPE_GROUP] = self->priv->groups; repos[TP_HANDLE_TYPE_LIST] = self->priv->lists; } static GPtrArray * _create_channel_managers (TpBaseConnection *connection) { GPtrArray *managers = g_ptr_array_new (); g_ptr_array_add (managers, connection); return managers; } static GHashTable * _get_channel_map (GruschlerFacebookConnection *self, TpHandleType handle_type) { switch (handle_type) { case TP_HANDLE_TYPE_LIST: return self->priv->list_channels; case TP_HANDLE_TYPE_GROUP: return self->priv->group_channels; default: break; } g_warning ("%s: invalid handle type %s", G_STRLOC, tp_handle_type_to_string (handle_type)); return NULL; } static void _contact_list_closed_cb (TpExportableChannel *channel, gpointer user_data) { GruschlerFacebookConnection *self; TpChannelManager *manager = user_data; gboolean destroyed; TpHandleType handle_type; TpHandle handle; self = GRUSCHLER_FACEBOOK_CONNECTION (manager); tp_channel_manager_emit_channel_closed_for_object (manager, channel); g_object_get (channel, "handle", &handle, "handle-type", &handle_type, "channel-destroyed", &destroyed, NULL); if (destroyed) { g_debug ("removing channel with %s %u", tp_handle_type_to_string (handle_type), handle); g_hash_table_remove (_get_channel_map (self, handle_type), GUINT_TO_POINTER (handle)); } else { g_debug ("reopening channel with handle %u", handle); tp_channel_manager_emit_new_channel (manager, channel, NULL); } } static TpExportableChannel * _create_contact_list (GruschlerFacebookConnection *self, TpHandleType handle_type, TpHandle handle, gpointer request_token) { TpExportableChannel *channel; GHashTable *channel_map; char *object_path; char *mangled_name; const char *handle_name; GSList *requests = NULL; TpHandleRepoIface *handle_repo; channel_map = _get_channel_map (self, handle_type); g_assert (!g_hash_table_lookup (channel_map, GUINT_TO_POINTER (handle))); handle_repo = tp_base_connection_get_handles (TP_BASE_CONNECTION (self), handle_type); handle_name = tp_handle_inspect (handle_repo, handle); mangled_name = tp_escape_as_identifier (handle_name); g_debug ("instantiating channel %s:%u \"%s\"", tp_handle_type_to_string (handle_type), handle, handle_name); object_path = g_strdup_printf ("%s/ContactList/%s/%s", TP_BASE_CONNECTION (self)->object_path, TP_HANDLE_TYPE_LIST == handle_type ? "List" : "Group", mangled_name); channel = g_object_new (GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST, "connection", self, "object-path", object_path, "handle", handle, "handle-type", handle_type, NULL); g_debug ("created %s", object_path); g_hash_table_insert (channel_map, GUINT_TO_POINTER (handle), channel); if (request_token) requests = g_slist_prepend (requests, request_token); /* TODO: check if list received */ tp_channel_manager_emit_new_channel (TP_CHANNEL_MANAGER (self), channel, requests); g_signal_connect (channel, "closed", G_CALLBACK (_contact_list_closed_cb), self); g_slist_free (requests); g_free (mangled_name); g_free (object_path); return channel; } static TpExportableChannel * _ensure_contact_list (GruschlerFacebookConnection *self, TpHandleType handle_type, TpHandle handle, gpointer request_token) { TpExportableChannel *channel; channel = g_hash_table_lookup (_get_channel_map (self, handle_type), GUINT_TO_POINTER (handle)); if (!channel) channel = _create_contact_list (self, handle_type, handle, request_token); return channel; } static char * _get_unique_connection_name (TpBaseConnection *connection) { GruschlerFacebookConnection *self; self = GRUSCHLER_FACEBOOK_CONNECTION (connection); return tp_escape_as_identifier (self->priv->email); } static char * _get_cache_dir (GruschlerFacebookConnection *self) { char *protocol, *unique_name, *filename; g_object_get (self, "protocol", &protocol, NULL); unique_name = _get_unique_connection_name (TP_BASE_CONNECTION (self)); filename = g_build_filename (g_get_user_cache_dir (), PACKAGE, protocol, unique_name, NULL); g_free (unique_name); g_free (protocol); return filename; } static char * _get_session_filename (GruschlerFacebookConnection *self) { char *cache_dir, *filename; cache_dir = _get_cache_dir (self); filename = g_build_filename (cache_dir, "session", NULL); g_free (cache_dir); return filename; } static const char * _get_self_uid (GruschlerFacebookConnection *self) { return tp_handle_inspect (self->priv->contacts, TP_BASE_CONNECTION (self)->self_handle); } static RestXmlNode * _get_profile (GruschlerFacebookConnection *self, TpHandle handle) { return g_hash_table_lookup (self->priv->profiles, GUINT_TO_POINTER (handle)); } static const char * _get_contact_alias (GruschlerFacebookConnection *self, TpHandle handle) { RestXmlNode *profile, *node = NULL; if (NULL != (profile = _get_profile (self, handle))) if (NULL != (node = rest_xml_node_find (profile, "name")) || NULL != (node = rest_xml_node_find (profile, "uid"))) return node->content; return NULL; } static GruschlerFacebookPresenceStatus _get_contact_presence (GruschlerFacebookConnection *self, TpHandle handle) { RestXmlNode *node = NULL; unsigned i; if (NULL != (node = _get_profile (self, handle)) && NULL != (node = rest_xml_node_find (node, "online_presence")) && NULL != (node->content)) { for (i = 0; _presence_statuses[i].name; ++i) if (!strcmp (_presence_statuses[i].name, node->content)) return i; } return GRUSCHLER_FACEBOOK_PRESENCE_UNKNOWN; } static const char * _get_contact_status (GruschlerFacebookConnection *self, TpHandle handle) { RestXmlNode *node = NULL; if (NULL != (node = _get_profile (self, handle)) && NULL != (node = rest_xml_node_find (node, "status")) && NULL != (node = rest_xml_node_find (node, "message"))) return node->content; return NULL; } static void _report_login_error (GruschlerFacebookConnection *self) { tp_base_connection_change_status (TP_BASE_CONNECTION (self), TP_CONNECTION_STATUS_DISCONNECTED, TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED); } static void _report_network_error (GruschlerFacebookConnection *self) { tp_base_connection_change_status (TP_BASE_CONNECTION (self), TP_CONNECTION_STATUS_DISCONNECTED, TP_CONNECTION_STATUS_REASON_NETWORK_ERROR); } static JsonNode * _get_json_root (RestProxyCall *call, const GError *transport_error, JsonNodeType expected_node_type, GType expected_value_type) { static JsonParser *parser = NULL; JsonNode *root = NULL; GError *error = NULL; JsonObject *object; if (transport_error) { g_warning ("Error from transport layer: %s", transport_error->message); goto OUT; } if (!parser) parser = json_parser_new (); const char *data = rest_proxy_call_get_payload (call); g_print ("data=%p\n", data); if (!json_parser_load_from_data (parser, rest_proxy_call_get_payload (call), rest_proxy_call_get_payload_length (call), &error)) { g_warning ("%s: Invalid response from Facebook: %s", G_STRLOC, error->message); g_error_free (error); goto OUT; } root = json_parser_get_root (parser); switch (json_node_get_node_type (root)) { case JSON_NODE_OBJECT: object = json_node_get_object (root); if (json_object_has_member (object, "error_msg")) { const char *msg = json_object_get_string_member (object, "error_msg"); gint64 code = json_object_get_int_member (object, "error_code"); g_warning ("%s: Error from facebook: %s (%" G_GINT64_FORMAT ")", G_STRLOC, msg, code); root = NULL; goto OUT; } break; case JSON_NODE_VALUE: if (expected_value_type != json_node_get_value_type (root)) { g_warning ("%s: Unexpected value type %s instead of %s", G_STRLOC, g_type_name (json_node_get_value_type (root)), g_type_name (expected_value_type)); root = NULL; goto OUT; } break; case JSON_NODE_ARRAY: case JSON_NODE_NULL: break; } if (expected_node_type != json_node_get_node_type (root)) { g_warning ("%s: Unexpected node type %d instead of node type %d", G_STRLOC, json_node_get_node_type (root), expected_node_type); root = NULL; goto OUT; } OUT: return root; } static RestXmlNode * _get_xml_root (RestProxyCall *call, const GError *error, const char *response_name) { static RestXmlParser *parser = NULL; RestXmlNode *root, *node; if (error) { g_warning ("Error from transport layer: %s", error->message); return NULL; } if (!parser) parser = rest_xml_parser_new (); root = rest_xml_parser_parse_from_data (parser, rest_proxy_call_get_payload (call), rest_proxy_call_get_payload_length (call)); if (!g_strcmp0 (root->name, "error_response")) { node = rest_xml_node_find (root, "error_msg"); g_warning ("Error from Facebook: %s", node->content); rest_xml_node_unref (root); root = NULL; } else if (g_strcmp0 (root->name, response_name)) { g_warning ("Unexpected response from Facebook: %s", root->name); rest_xml_node_unref (root); root = NULL; } return root; } static void _update_uids (GruschlerFacebookConnection *self, RestXmlNode *result) { TpIntSet *added, *removed; RestXmlNode *node, *uid; TpExportableChannel *channel; TpHandle handle; added = tp_intset_new (); removed = tp_intset_new (); for (node = rest_xml_node_find (result, "friend_info"); node; node = node->next) { if (g_strcmp0 (node->name, "friend_info")) continue; uid = rest_xml_node_find (node, "uid2"); if (!uid) continue; handle = tp_handle_ensure (self->priv->contacts, uid->content, NULL, NULL); /* TODO: really check changes */ tp_intset_add (added, handle); } channel = _ensure_contact_list (self, TP_HANDLE_TYPE_LIST, GRUSCHLER_FACEBOOK_LIST_HANDLE_PUBLISH, NULL); tp_group_mixin_change_members (G_OBJECT (channel), NULL, added, removed, NULL, NULL, TP_BASE_CONNECTION (self)->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); channel = _ensure_contact_list (self, TP_HANDLE_TYPE_LIST, GRUSCHLER_FACEBOOK_LIST_HANDLE_SUBSCRIBE, NULL); tp_group_mixin_change_members (G_OBJECT (channel), NULL, added, removed, NULL, NULL, TP_BASE_CONNECTION (self)->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); channel = _ensure_contact_list (self, TP_HANDLE_TYPE_LIST, GRUSCHLER_FACEBOOK_LIST_HANDLE_STORED, NULL); tp_group_mixin_change_members (G_OBJECT (channel), NULL, added, removed, NULL, NULL, TP_BASE_CONNECTION (self)->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); tp_intset_destroy (removed); tp_intset_destroy (added); } static void _update_profiles (GruschlerFacebookConnection *self, RestXmlNode *result) { RestXmlNode *user, *uid, *name; GValue entry = { 0, }; GPtrArray *aliases; TpHandle handle; g_value_init (&entry, TP_STRUCT_TYPE_ALIAS_PAIR); g_value_take_boxed (&entry, dbus_g_type_specialized_construct (TP_STRUCT_TYPE_ALIAS_PAIR)); aliases = g_ptr_array_new (); for (user = rest_xml_node_find (result, "user"); user; user = user->next) { if (g_strcmp0 (user->name, "user")) continue; uid = rest_xml_node_find (user, "uid"); name = rest_xml_node_find (user, "name"); if (!uid) continue; handle = tp_handle_ensure (self->priv->contacts, uid->content, NULL, NULL); g_hash_table_insert (self->priv->profiles, GUINT_TO_POINTER (handle), rest_xml_node_ref (user)); dbus_g_type_struct_set (&entry, 0, handle, 1, name ? name->content : uid->content, G_MAXUINT); g_ptr_array_add (aliases, g_value_dup_boxed (&entry)); } tp_svc_connection_interface_aliasing_emit_aliases_changed (self, aliases); g_ptr_array_free (aliases, TRUE); g_value_unset (&entry); } static void _update_friends_cb (RestProxyCall *call, GError *error, GObject *weak_object, gpointer user_data) { GruschlerFacebookConnection *self; RestXmlNode *root, *result, *name; self = GRUSCHLER_FACEBOOK_CONNECTION (weak_object); root = _get_xml_root (call, error, "fql_multiquery_response"); if (!root) { _report_network_error (self); return; } for (result = rest_xml_node_find (root, "fql_result"); result; result = result->next) { name = rest_xml_node_find (result, "name"); if (!name) continue; if (!g_strcmp0 (name->content, "uids")) { _update_uids (self, result); continue; } if (!g_strcmp0 (name->content, "profiles")) { _update_profiles (self, result); continue; } } rest_xml_node_unref (root); } static void _update_friends (GruschlerFacebookConnection *self) { GError *error = NULL; GString *queries; RestProxyCall *call; queries = g_string_new (NULL); g_string_append_c (queries, '{'); g_string_append_printf (queries, "\"uids\":" "\"SELECT uid2 FROM friend WHERE uid1=%s\"", _get_self_uid (self)); g_string_append_c (queries, ','); g_string_append_printf (queries, "\"profiles\":" "\"SELECT uid,name,profile_update_time,online_presence,status " "FROM user WHERE uid=%s OR uid IN (SELECT uid2 FROM friend WHERE uid1=%s)\"", _get_self_uid (self), _get_self_uid (self)); /* TODO: "AND profile_update_time > 0" */ g_string_append_c (queries, '}'); g_debug ("queries=%s\n", queries->str); call = rest_proxy_new_call (self->priv->facebook); rest_proxy_call_set_function (call, "fql.multiquery"); rest_proxy_call_add_param (call, "auth_token", self->priv->token); rest_proxy_call_add_param (call, "queries", queries->str); if (! rest_proxy_call_async (call, _update_friends_cb, G_OBJECT (self), NULL, &error)) { /* TODO: properly handle errors */ g_warning ("%s: %s", G_STRLOC, error->message); g_error_free (error); } g_string_free (queries, TRUE); g_object_unref (call); } #if 0 static void _auth_get_session_cb (RestProxyCall *call, GError *error, GObject *weak_object, gpointer user_data) { GruschlerFacebookConnection *self; const char *session_key = NULL; const char *secret = NULL; const char *uid = NULL; RestXmlNode *root, *node; TpHandle handle; self = GRUSCHLER_FACEBOOK_CONNECTION (weak_object); root = _get_xml_root (call, error, "auth_getSession_response"); if (!root) { _report_network_error (self); return; } if (NULL != (node = rest_xml_node_find (root, "session_key"))) session_key = node->content; if (NULL != (node = rest_xml_node_find (root, "secret"))) secret = node->content; if (NULL != (node = rest_xml_node_find (root, "uid"))) uid = node->content; if (!session_key || !secret || !uid) { _report_network_error (self); return; } g_debug ("got new secret %s and session key %s for %s\n", secret, session_key, uid); facebook_proxy_set_session_key (FACEBOOK_PROXY (self->priv->facebook), session_key); facebook_proxy_set_app_secret (FACEBOOK_PROXY (self->priv->facebook), secret); handle = tp_handle_ensure (self->priv->contacts, uid, NULL, NULL); tp_base_connection_set_self_handle (TP_BASE_CONNECTION (self), handle); tp_base_connection_change_status (TP_BASE_CONNECTION (self), TP_CONNECTION_STATUS_CONNECTED, TP_CONNECTION_STATUS_REASON_REQUESTED); rest_xml_node_unref (root); _update_friends (self); } static void _post_login_data_cb (SoupSession *session, SoupMessage *message, gpointer user_data) { GruschlerFacebookConnection *self = user_data; GError *error = NULL; RestProxyCall *call; SoupURI *uri; uri = soup_message_get_uri (message); g_debug ("status=%d length=%lld", message->status_code, message->response_body->length); g_debug ("uri=%s query=%s", uri->path, uri->query); g_file_set_contents ("/tmp/fb-login.html", message->response_body->data, message->response_body->length, NULL); if (!g_strcmp0 (uri->path, "/login.php")) { tp_base_connection_change_status (TP_BASE_CONNECTION (self), TP_CONNECTION_STATUS_DISCONNECTED, TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED); } else if (!g_strcmp0 (uri->path, "/desktopapp.php") && !g_strcmp0 (uri->query, "api_key=" GRUSCHLER_FACEBOOK_APIKEY)) { call = rest_proxy_new_call (self->priv->facebook); rest_proxy_call_set_function (call, "auth.getSession"); rest_proxy_call_add_param (call, "auth_token", self->priv->token); rest_proxy_call_async (call, _auth_get_session_cb, G_OBJECT (self), NULL, &error); /* TODO: handle error */ } else _report_network_error (self); } static GHashTable * _parse_form (const char *data, gsize length) { GRegex *regex_field, *regex_attrs; GMatchInfo *match_field, *match_attrs; GHashTable *params; params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); regex_field = g_regex_new ("", G_REGEX_CASELESS | G_REGEX_DOTALL, 0, NULL); regex_attrs = g_regex_new ("\\b(\\S+?)=\"(.*?)\"", G_REGEX_CASELESS | G_REGEX_DOTALL, 0, NULL); if (g_regex_match_full (regex_field, data, length, 0, 0, &match_field, NULL)) { do { char *name = NULL; char *value = NULL; int start, end; g_match_info_fetch_pos (match_field, 1, &start, &end); if (g_regex_match_full (regex_attrs, data + start, end - start, 0, 0, &match_attrs, NULL)) { do { char *param = g_match_info_fetch (match_attrs, 1); if (!g_strcmp0 (param, "name")) name = g_match_info_fetch (match_attrs, 2); else if (!g_strcmp0 (param, "value")) value = g_match_info_fetch (match_attrs, 2); g_free (param); } while (g_match_info_next (match_attrs, NULL)); } g_match_info_free (match_attrs); if (!name || !value) { g_free (value); g_free (name); continue; } g_hash_table_insert (params, name, value); } while (g_match_info_next (match_field, NULL)); } g_match_info_free (match_field); g_regex_unref (regex_attrs); g_regex_unref (regex_field); return params; } static void _fetch_login_form_cb (SoupSession *session, SoupMessage *message, gpointer user_data) { GruschlerFacebookConnection *self = user_data; char *form = NULL; char *url = NULL; int start, end; GHashTable *params; GRegex *regex; GMatchInfo *match; if (SOUP_STATUS_OK != message->status_code) { g_warning ("cannot fetch login form: %s", message->reason_phrase); _report_network_error (self); return; } regex = g_regex_new ("(.*?)", G_REGEX_CASELESS | G_REGEX_DOTALL, 0, NULL); if (g_regex_match_full (regex, message->response_body->data, message->response_body->length, 0, 0, &match, NULL)) { url = g_match_info_fetch (match, 1); g_match_info_fetch_pos (match, 2, &start, &end); } g_match_info_free (match); g_regex_unref (regex); if (!url) { g_warning ("cannot find login URL"); _report_network_error (self); return; } params = _parse_form (message->response_body->data + start, end - start); g_hash_table_insert (params, g_strdup ("email"), g_strdup (self->priv->email)); g_hash_table_insert (params, g_strdup ("pass"), g_strdup (self->priv->password)); g_hash_table_remove (params, "charset_test"); form = soup_form_encode_hash (params); g_hash_table_unref (params); message = soup_message_new (SOUP_METHOD_POST, url); soup_message_set_request (message, "application/x-www-form-urlencoded", SOUP_MEMORY_TAKE, form, strlen (form)); soup_session_queue_message (self->priv->session, message, _post_login_data_cb, self); g_object_unref (message); g_free (url); } static void _auth_create_token_cb (RestProxyCall *call, GError *error, GObject *weak_object, gpointer user_data) { GruschlerFacebookConnection *self; RestXmlNode *root; SoupMessage *message; char *url; self = GRUSCHLER_FACEBOOK_CONNECTION (weak_object); root = _get_xml_root (call, error, "auth_createToken_response"); if (!root) { _report_network_error (self); return; } g_free (self->priv->token); self->priv->token = g_strdup (root->content); rest_xml_node_unref (root); g_debug ("login token is %s", self->priv->token); url = facebook_proxy_build_login_url (FACEBOOK_PROXY (self->priv->facebook), self->priv->token); message = soup_message_new (SOUP_METHOD_GET, url); soup_session_queue_message (self->priv->session, message, _fetch_login_form_cb, self); g_object_unref (message); g_free (url); } #endif static void _start_session (GruschlerFacebookConnection *self, const char *session, const char *secret, const char *uid) { TpHandle handle; if (session) { g_debug ("session key: %s", session); facebook_proxy_set_session_key (FACEBOOK_PROXY (self->priv->facebook), session); } if (secret) { g_debug ("session secret: %s", secret); facebook_proxy_set_app_secret (FACEBOOK_PROXY (self->priv->facebook), secret); } if (uid) { g_debug ("session uid: %s", uid); handle = tp_handle_ensure (self->priv->contacts, uid, NULL, NULL); tp_base_connection_set_self_handle (TP_BASE_CONNECTION (self), handle); tp_base_connection_change_status (TP_BASE_CONNECTION (self), TP_CONNECTION_STATUS_CONNECTED, TP_CONNECTION_STATUS_REASON_REQUESTED); _update_friends (self); } } static void _users_get_logged_in_user_cb (RestProxyCall *call, GError *error, GObject *weak_object, gpointer user_data) { GruschlerFacebookConnection *self; JsonNode *root; char *uid = NULL; self = GRUSCHLER_FACEBOOK_CONNECTION (weak_object); root = _get_json_root (call, error, JSON_NODE_VALUE, G_TYPE_INT64); g_print ("%s\n", g_type_name (json_node_get_value_type (root))); g_print ("%s\n", json_node_get_string (root)); g_print ("%" G_GINT64_FORMAT "\n", json_node_get_int (root)); if (root) uid = g_strdup_printf ("%" G_GINT64_FORMAT, json_node_get_int (root)); if (uid) { g_debug ("resuming session for %s", uid); _start_session (self, NULL, NULL, uid); } g_free (uid); } static gboolean _parse_session_string (const char *data, gsize length, char **session_out, char **secret_out, char **uid_out) { GError *error = NULL; JsonParser *parser = NULL; JsonNode *root = NULL; JsonObject *obj = NULL; gboolean success = FALSE; g_return_val_if_fail (NULL != data, FALSE); parser = json_parser_new (); if (!json_parser_load_from_data (parser, data, length, &error)) { g_warning ("%s: %s\n", G_STRLOC, error->message); g_error_free (error); goto OUT; } root = json_parser_get_root (parser); if (root && JSON_NODE_OBJECT == json_node_get_node_type (root)) obj = json_node_get_object (root); if (!obj || !json_object_has_member (obj, "session_key") || !json_object_has_member (obj, "secret") || !json_object_has_member (obj, "uid")) { g_warning ("%s: Invalid session string\n", G_STRLOC); goto OUT; } if (session_out) *session_out = g_strdup (json_object_get_string_member (obj, "session_key")); if (session_out) *secret_out = g_strdup (json_object_get_string_member (obj, "secret")); if (uid_out) *uid_out = g_strdup (json_object_get_string_member (obj, "uid")); success = TRUE; OUT: if (parser) g_object_unref (parser); return success; } static void _login_helper_watch_cb (GPid pid, int status, gpointer user_data) { GruschlerFacebookConnection *self = user_data; gboolean success = FALSE; char buffer[1024]; const char *start, *end; gsize length; g_spawn_close_pid (pid); g_return_if_fail (pid == self->priv->login_helper_pid); if (g_input_stream_read_all (self->priv->login_helper_stream, buffer, sizeof buffer, &length, NULL, NULL)) { if (!(start = g_strstr_len (buffer, length, "GRUSCHLER-SESSION:"))) goto OUT; start += strlen ("GRUSCHLER-SESSION:"); for (end = start; end < buffer + length; ++end) if ('\n' == *end) break; if ('\n' == *end) { char *key = NULL, *secret = NULL, *uid = NULL; if (_parse_session_string (start, end - start, &key, &secret, &uid)) { char *filename; filename = _get_cache_dir (self); g_mkdir_with_parents (filename, 0700); g_free (filename); filename = _get_session_filename (self); g_file_set_contents (filename, start, end - start, NULL); g_free (filename); g_debug ("starting new session for %s", uid); _start_session (self, key, secret, uid); success = TRUE; } g_free (secret); g_free (uid); g_free (key); goto OUT; } } OUT: if (!success) _report_login_error (self); g_object_unref (self->priv->login_helper_stream); self->priv->login_helper_stream = 0; self->priv->login_helper_pid = 0; self->priv->login_helper_id = 0; } static gboolean _run_login_helper (GruschlerFacebookConnection *self, GError **error) { char *argv[] = { TELEPATHY_LIBDIR "/telepathy-gruschler-login", NULL }; int fd; g_return_val_if_fail (0 == self->priv->login_helper_pid, FALSE); g_return_val_if_fail (0 == self->priv->login_helper_id, FALSE); g_debug ("running login helper"); if (!g_spawn_async_with_pipes (NULL, argv, NULL, G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &self->priv->login_helper_pid, NULL, &fd, NULL, error)) return FALSE; self->priv->login_helper_id = g_child_watch_add (self->priv->login_helper_pid, _login_helper_watch_cb, self); self->priv->login_helper_stream = g_unix_input_stream_new (fd, TRUE); return TRUE; } static gboolean _start_connecting (TpBaseConnection *connection, GError **error_out) { GruschlerFacebookConnection *self; GError *error = NULL; RestProxyCall *call = NULL; gboolean success = FALSE; char *filename = NULL; char *data = NULL; gsize length; self = GRUSCHLER_FACEBOOK_CONNECTION (connection); if (self->priv->facebook || self->priv->session) { g_set_error_literal (error_out, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "invalid state: session already exists"); goto OUT; } self->priv->facebook = facebook_proxy_new (GRUSCHLER_FACEBOOK_APIKEY, NULL); // FIXME g_object_set (self->priv->facebook, "url-format", "http://api.facebook.com/restserver.php", NULL); self->priv->session = soup_session_sync_new_with_options (SOUP_SESSION_USER_AGENT, "Firefox/3.0 ", SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_COOKIE_JAR, NULL); filename = _get_session_filename (self); if (g_file_get_contents (filename, &data, &length, NULL)) { char *key = NULL, *secret = NULL; if (_parse_session_string (data, length, &key, &secret, NULL)) { facebook_proxy_set_session_key (FACEBOOK_PROXY (self->priv->facebook), key); facebook_proxy_set_app_secret (FACEBOOK_PROXY (self->priv->facebook), secret); g_print ("key=%s\n", key); g_print ("secret=%s\n", secret); call = rest_proxy_new_call (self->priv->facebook); rest_proxy_call_set_function (call, "users.getLoggedInUser"); rest_proxy_call_add_param (call, "format", "JSON"); success = rest_proxy_call_async (call, _users_get_logged_in_user_cb, G_OBJECT (self), NULL, &error); } g_free (secret); g_free (key); } if (!success) success = _run_login_helper (self, &error); OUT: if (error) { g_warning ("%s: %s (%s:%d)", G_STRLOC, error->message, g_quark_to_string (error->domain), error->code); g_set_error (error_out, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "%s (%s:%d)", error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); } else if (!success) g_assert (!error_out || *error_out); if (call) g_object_unref (call); g_free (filename); g_free (data); return success; } static void _shut_down (TpBaseConnection *connection) { GruschlerFacebookConnection *self; self = GRUSCHLER_FACEBOOK_CONNECTION (connection); if (self->priv->facebook) { g_object_unref (self->priv->facebook); self->priv->facebook = NULL; } } static void _foreach_channel (TpChannelManager *manager, TpExportableChannelFunc callback, gpointer user_data) { GruschlerFacebookConnection *self; GHashTableIter iter; TpExportableChannel *channel; self = GRUSCHLER_FACEBOOK_CONNECTION (manager); g_return_if_fail (NULL != self->priv->list_channels); g_return_if_fail (NULL != self->priv->group_channels); g_hash_table_iter_init (&iter, self->priv->list_channels); while (g_hash_table_iter_next (&iter, NULL, (gpointer) &channel)) callback (channel, user_data); g_hash_table_iter_init (&iter, self->priv->group_channels); while (g_hash_table_iter_next (&iter, NULL, (gpointer) &channel)) callback (channel, user_data); } static void _foreach_channel_class (TpChannelManager *manager, TpChannelManagerChannelClassFunc callback, gpointer user_data) { GHashTable *fixed; fixed = tp_asv_new (_fixed_properties[0], G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST, _fixed_properties[1], G_TYPE_UINT, TP_HANDLE_TYPE_LIST, NULL); callback (manager, fixed, _allowed_properties, user_data); tp_asv_set_uint32 (fixed, _fixed_properties[1], TP_HANDLE_TYPE_GROUP); callback (manager, fixed, _allowed_properties, user_data); g_hash_table_unref (fixed); } static gboolean _handle_channel_request (TpChannelManager *manager, gpointer request_token, GHashTable *request_properties, gboolean require_new) { GruschlerFacebookConnection *self; GError *error = NULL; TpExportableChannel *channel = NULL; const char *channel_type; TpHandleType handle_type; TpHandleRepoIface *handle_repo; TpHandle handle; self = GRUSCHLER_FACEBOOK_CONNECTION (manager); channel_type = tp_asv_get_string (request_properties, TP_IFACE_CHANNEL ".ChannelType"); handle_type = tp_asv_get_uint32 (request_properties, TP_IFACE_CHANNEL ".TargetHandleType", NULL); if (handle_type != TP_HANDLE_TYPE_LIST && handle_type != TP_HANDLE_TYPE_GROUP) return FALSE; if (g_strcmp0 (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST)) return FALSE; /* Check if the handle is valid */ handle = tp_asv_get_uint32 (request_properties, TP_IFACE_CHANNEL ".TargetHandle", NULL); handle_repo = tp_base_connection_get_handles (TP_BASE_CONNECTION (self), handle_type); if (!tp_handle_is_valid (handle_repo, handle, &error)) goto OUT; /* Check if there are any other properties that we don't understand */ if (tp_channel_manager_asv_has_unknown_properties (request_properties, _fixed_properties, _allowed_properties, &error)) goto OUT; channel = g_hash_table_lookup (_get_channel_map (self, handle_type), GUINT_TO_POINTER (handle)); if (!channel) { _create_contact_list (self, handle_type, handle, request_token); return TRUE; } if (require_new) g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "Contact list #%u has been created already", handle); OUT: if (channel) tp_channel_manager_emit_request_already_satisfied (self, request_token, channel); else { tp_channel_manager_emit_request_failed (self, request_token, error->domain, error->code, error->message); g_error_free (error); } return TRUE; } static gboolean _create_channel (TpChannelManager *manager, gpointer request_token, GHashTable *request_properties) { return _handle_channel_request (manager, request_token, request_properties, TRUE); } static gboolean _request_channel (TpChannelManager *manager, gpointer request_token, GHashTable *request_properties) { return _handle_channel_request (manager, request_token, request_properties, FALSE); } static gboolean _ensure_channel (TpChannelManager *manager, gpointer request_token, GHashTable *request_properties) { return _handle_channel_request (manager, request_token, request_properties, FALSE); } static void _channel_manager_iface_init (TpChannelManagerIface *iface) { iface->foreach_channel = _foreach_channel; iface->foreach_channel_class = _foreach_channel_class; iface->create_channel = _create_channel; iface->request_channel = _request_channel; iface->ensure_channel = _ensure_channel; } static void _aliasing_get_alias_flags (TpSvcConnectionInterfaceAliasing *aliasing, DBusGMethodInvocation *context) { tp_svc_connection_interface_aliasing_return_from_get_alias_flags (context, 0); } static void _aliasing_request_aliases (TpSvcConnectionInterfaceAliasing *aliasing, const GArray *contacts, DBusGMethodInvocation *context) { GruschlerFacebookConnection *self; const char **aliases; TpHandle handle; int i; self = GRUSCHLER_FACEBOOK_CONNECTION (aliasing); aliases = g_new0 (const char *, contacts->len + 1); for (i = 0; i < contacts->len; ++i) { handle = g_array_index (contacts, TpHandle, i); aliases[i] = _get_contact_alias (self, handle); } tp_svc_connection_interface_aliasing_return_from_request_aliases (context, aliases); g_free (aliases); } static void _aliasing_get_aliases (TpSvcConnectionInterfaceAliasing *aliasing, const GArray *contacts, DBusGMethodInvocation *context) { GruschlerFacebookConnection *self; GHashTable *aliases; TpHandle handle; const char *name; int i; self = GRUSCHLER_FACEBOOK_CONNECTION (aliasing); aliases = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); for (i = 0; i < contacts->len; ++i) { handle = g_array_index (contacts, TpHandle, i); name = _get_contact_alias (self, handle); if (!name) continue; g_hash_table_insert (aliases, GUINT_TO_POINTER (handle), g_strdup (name)); } tp_svc_connection_interface_aliasing_return_from_get_aliases (context, aliases); g_hash_table_unref (aliases); } static void _aliasing_set_aliases (TpSvcConnectionInterfaceAliasing *aliasing, GHashTable *aliases, DBusGMethodInvocation *context) { GError error = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, "you cannot modify Facebook aliases" }; dbus_g_method_return_error (context, &error); } static void _aliasing_fill_contact_attributes (GObject *object, const GArray *contacts, GHashTable *attributes) { GruschlerFacebookConnection *self; unsigned i; self = GRUSCHLER_FACEBOOK_CONNECTION (object); for (i = 0; i < contacts->len; ++i) { TpHandle handle; GValue *value; value = tp_g_value_slice_new (G_TYPE_STRING); handle = g_array_index (contacts, TpHandle, i); g_value_set_string (value, _get_contact_alias (self, handle)); tp_contacts_mixin_set_contact_attribute (attributes, handle, TP_IFACE_CONNECTION_INTERFACE_ALIASING "/alias", value); } } static void _aliasing_iface_init (TpSvcConnectionInterfaceAliasingClass *iface) { #define IMPLEMENT(x) \ tp_svc_connection_interface_aliasing_implement_##x (iface, _aliasing_##x) IMPLEMENT (get_alias_flags); IMPLEMENT (request_aliases); IMPLEMENT (get_aliases); IMPLEMENT (set_aliases); #undef IMPLEMENT } static GHashTable * _presence_get_contact_statuses (GObject *object, const GArray *contacts, GError **error) { GruschlerFacebookConnection *self; TpBaseConnection *base; GHashTable *statuses; unsigned i; statuses = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) tp_presence_status_free); self = GRUSCHLER_FACEBOOK_CONNECTION (object); base = TP_BASE_CONNECTION (self); for (i = 0; i < contacts->len; i++) { TpPresenceStatus *presence; GruschlerFacebookPresenceStatus status = GRUSCHLER_FACEBOOK_PRESENCE_UNKNOWN; const char *message = NULL; GHashTable *parameters; TpHandle handle; handle = g_array_index (contacts, TpHandle, i); status = _get_contact_presence (self, handle); message = _get_contact_status (self, handle); parameters = tp_asv_new (NULL, NULL); if (message) tp_asv_set_string (parameters, "message", message); presence = tp_presence_status_new (status, parameters); g_hash_table_unref (parameters); g_hash_table_insert (statuses, GUINT_TO_POINTER (handle), presence); } return statuses; } static gboolean _presence_set_own_status (GObject *object, const TpPresenceStatus *status, GError **error) { /* FIXME: implement this */ g_warning ("%s: not implemented yet :-(", G_STRFUNC); g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, "Setting own presence status is not implemented yet"); return FALSE; } static GObject * _constructor (GType type, guint n_props, GObjectConstructParam *props) { GruschlerFacebookConnection *self; GObject *object; GObjectClass *parent_class; gsize offset; parent_class = G_OBJECT_CLASS (gruschler_facebook_connection_parent_class); object = parent_class->constructor (type, n_props, props); self = GRUSCHLER_FACEBOOK_CONNECTION (object); offset = G_STRUCT_OFFSET (GruschlerFacebookConnection, contacts); tp_contacts_mixin_init (object, offset); offset = G_STRUCT_OFFSET (GruschlerFacebookConnection, presence); tp_presence_mixin_init (object, offset); tp_base_connection_register_with_contacts_mixin (TP_BASE_CONNECTION (self)); tp_presence_mixin_simple_presence_register_with_contacts_mixin (object); tp_contacts_mixin_add_contact_attributes_iface (object, TP_IFACE_CONNECTION_INTERFACE_ALIASING, _aliasing_fill_contact_attributes); return object; } static void _set_property (GObject *object, unsigned prop_id, const GValue *value, GParamSpec *pspec) { GruschlerFacebookConnection *self; self = GRUSCHLER_FACEBOOK_CONNECTION (object); switch (prop_id) { case PROP_EMAIL: g_free (self->priv->email); self->priv->email = g_value_dup_string (value); break; case PROP_PASSWORD: g_free (self->priv->password); self->priv->password = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void _get_property (GObject *object, unsigned prop_id, GValue *value, GParamSpec *pspec) { GruschlerFacebookConnection *self; self = GRUSCHLER_FACEBOOK_CONNECTION (object); switch (prop_id) { case PROP_EMAIL: g_value_set_string (value, self->priv->email); break; case PROP_PASSWORD: g_value_set_string (value, self->priv->password); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void _dispose (GObject *object) { GObjectClass *object_class; GruschlerFacebookConnection *self; self = GRUSCHLER_FACEBOOK_CONNECTION (object); if (self->priv->login_helper_stream) { g_object_unref (self->priv->login_helper_stream); self->priv->login_helper_stream = NULL; } if (self->priv->login_helper_id) { g_source_remove (self->priv->login_helper_id); self->priv->login_helper_id = 0; } if (self->priv->login_helper_pid) { g_spawn_close_pid (self->priv->login_helper_pid); self->priv->login_helper_pid = 0; } object_class = G_OBJECT_CLASS (gruschler_facebook_connection_parent_class); object_class->dispose (object); } static void _finalize (GObject *object) { GObjectClass *object_class; GruschlerFacebookConnection *self; self = GRUSCHLER_FACEBOOK_CONNECTION (object); tp_contacts_mixin_finalize (object); tp_presence_mixin_finalize (object); if (self->priv->facebook) g_object_unref (self->priv->facebook); if (self->priv->session) g_object_unref (self->priv->session); if (self->priv->contacts) g_object_unref (self->priv->contacts); if (self->priv->groups) g_object_unref (self->priv->groups); if (self->priv->lists) g_object_unref (self->priv->lists); g_hash_table_unref (self->priv->list_channels); g_hash_table_unref (self->priv->group_channels); g_hash_table_unref (self->priv->profiles); g_free (self->priv->email); g_free (self->priv->password); g_free (self->priv->token); object_class = G_OBJECT_CLASS (gruschler_facebook_connection_parent_class); object_class->finalize (object); } static void gruschler_facebook_connection_class_init (GruschlerFacebookConnectionClass *class) { static const char *interfaces[] = { TP_IFACE_CONNECTION_INTERFACE_ALIASING, TP_IFACE_CONNECTION_INTERFACE_CONTACTS, TP_IFACE_CONNECTION_INTERFACE_PRESENCE, TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE, #if 0 TP_IFACE_CONNECTION_INTERFACE_AVATARS, TP_IFACE_CONNECTION_INTERFACE_LOCATION, RTCOM_TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO, #endif NULL }; static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { #if 0 { TP_IFACE_CONNECTION_INTERFACE_LOCATION, conn_location_properties_getter, conn_location_properties_setter, location_props, }, { TP_IFACE_CONNECTION_INTERFACE_AVATARS, conn_avatars_properties_getter, NULL, avatar_props, }, #endif { NULL, } }; GParamSpec *pspec; GObjectClass *object_class; TpBaseConnectionClass *connection_class; gsize offset; object_class = G_OBJECT_CLASS (class); object_class->constructor = _constructor; object_class->set_property = _set_property; object_class->get_property = _get_property; object_class->dispose = _dispose; object_class->finalize = _finalize; connection_class = TP_BASE_CONNECTION_CLASS (class); connection_class->create_handle_repos = _create_handle_repos; connection_class->create_channel_managers = _create_channel_managers; connection_class->get_unique_connection_name = _get_unique_connection_name; connection_class->start_connecting = _start_connecting; connection_class->shut_down = _shut_down; connection_class->interfaces_always_present = interfaces; pspec = g_param_spec_string ("email", "Email", "Email address for accessing Facebook", GRUSCHLER_FACEBOOK_DEFAULT_EMAIL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_EMAIL, pspec); pspec = g_param_spec_string ("password", "Password", "Password for accessing Facebook", GRUSCHLER_FACEBOOK_DEFAULT_PASSWORD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_PASSWORD, pspec); class->properties_class.interfaces = prop_interfaces; offset = G_STRUCT_OFFSET (GruschlerFacebookConnectionClass, properties_class); tp_dbus_properties_mixin_class_init (object_class, offset); offset = G_STRUCT_OFFSET (GruschlerFacebookConnectionClass, contacts_class); tp_contacts_mixin_class_init (object_class, offset); offset = G_STRUCT_OFFSET (GruschlerFacebookConnectionClass, presence_class); tp_presence_mixin_class_init (object_class, offset, NULL, _presence_get_contact_statuses, _presence_set_own_status, _presence_statuses); tp_presence_mixin_simple_presence_init_dbus_properties (object_class); g_type_class_add_private (class, sizeof (GruschlerFacebookConnectionPrivate)); } static void gruschler_facebook_connection_init (GruschlerFacebookConnection *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GRUSCHLER_TYPE_FACEBOOK_CONNECTION, GruschlerFacebookConnectionPrivate); self->priv->list_channels = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); self->priv->group_channels = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); self->priv->profiles = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) rest_xml_node_unref); } TpBaseConnection * gruschler_facebook_connection_new (TpBaseConnectionManager *cm, const GruschlerFacebookConnectionParams *params, GError **error) { g_return_val_if_fail (TP_IS_BASE_CONNECTION_MANAGER (cm), NULL); g_return_val_if_fail (NULL != params, NULL); if (!params->email || !params->email[0]) { g_set_error_literal (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "mandatory email argument is empty"); return NULL; } if (!params->password || !params->password[0]) { g_set_error_literal (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "mandatory password argument is empty"); return NULL; } return g_object_new (GRUSCHLER_TYPE_FACEBOOK_CONNECTION, "protocol", "facebook", "email", params->email, "password", params->password, NULL); } void * gruschler_facebook_connection_params_new (void) { return g_new0 (GruschlerFacebookConnectionParams, 1); } void gruschler_facebook_connection_params_free (void *parsed_params) { GruschlerFacebookConnectionParams *params = parsed_params; g_free (params->email); g_free (params->password); g_free (params); }