Fix crashes when filenames end up being NULL in some prpls.
[pidgin-git.git] / libpurple / protocols / bonjour / mdns_common.c
blob53bcd7296e0067e4c500a685ac30966e9f29376f
1 /*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU Library General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
17 #include <string.h>
19 #include "internal.h"
20 #include "debug.h"
22 #include "mdns_common.h"
23 #include "mdns_interface.h"
24 #include "bonjour.h"
25 #include "buddy.h"
28 /**
29 * Allocate space for the dns-sd data.
31 BonjourDnsSd * bonjour_dns_sd_new() {
32 BonjourDnsSd *data = g_new0(BonjourDnsSd, 1);
33 return data;
36 /**
37 * Deallocate the space of the dns-sd data.
39 void bonjour_dns_sd_free(BonjourDnsSd *data) {
40 g_free(data->first);
41 g_free(data->last);
42 g_free(data->phsh);
43 g_free(data->status);
44 g_free(data->vc);
45 g_free(data->msg);
46 g_free(data);
49 #define MAX_TXT_CONSTITUENT_LEN 255
51 /* Make sure that the value isn't longer than it is supposed to be */
52 static const char*
53 get_max_txt_record_value(const char *key, const char *value)
55 /* "each constituent string of a DNS TXT record is limited to 255 bytes"
56 * This includes the key and the '='
58 static char buffer[MAX_TXT_CONSTITUENT_LEN + 1];
59 gchar *end_valid = NULL;
60 int len = MIN(strlen(value), MAX_TXT_CONSTITUENT_LEN - (strlen(key) + 2));
62 strncpy(buffer, value, len);
64 buffer[len] = '\0';
66 /* If we've cut part of a utf-8 character, kill it */
67 if (!g_utf8_validate(buffer, -1, (const gchar **)&end_valid))
68 *end_valid = '\0';
70 return buffer;
73 static GSList *generate_presence_txt_records(BonjourDnsSd *data) {
74 GSList *ret = NULL;
75 PurpleKeyValuePair *kvp;
76 char portstring[6];
77 const char *jid, *aim, *email;
79 /* Convert the port to a string */
80 snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj);
82 jid = purple_account_get_string(data->account, "jid", NULL);
83 aim = purple_account_get_string(data->account, "AIM", NULL);
84 email = purple_account_get_string(data->account, "email", NULL);
86 #define _M_ADD_R(k, v) \
87 kvp = g_new0(PurpleKeyValuePair, 1); \
88 kvp->key = g_strdup(k); \
89 kvp->value = g_strdup(get_max_txt_record_value(k, v)); \
90 ret = g_slist_prepend(ret, kvp); \
92 /* We should try to follow XEP-0174, but some clients have "issues", so we humor them.
93 * See http://telepathy.freedesktop.org/wiki/SalutInteroperability
96 /* Large TXT records are problematic.
97 * While it is technically possible for this to exceed a standard 512-byte
98 * DNS message, it shouldn't happen unless we get wacky data entered for
99 * some of the freeform fields. It is even less likely to exceed the
100 * recommended maximum of 1300 bytes.
103 /* Needed by iChat */
104 _M_ADD_R("txtvers", "1")
105 /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
106 _M_ADD_R("1st", data->first)
107 /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
108 _M_ADD_R("last", data->last)
109 /* Needed by Adium */
110 _M_ADD_R("port.p2pj", portstring)
111 /* Needed by iChat, Gaim/Pidgin <= 2.0.1 */
112 _M_ADD_R("status", data->status)
113 _M_ADD_R("node", "libpurple")
114 _M_ADD_R("ver", VERSION)
115 /* Currently always set to "!" since we don't support AV and wont ever be in a conference */
116 _M_ADD_R("vc", data->vc)
117 if (email != NULL && *email != '\0') {
118 _M_ADD_R("email", email)
120 if (jid != NULL && *jid != '\0') {
121 _M_ADD_R("jid", jid)
123 /* Nonstandard, but used by iChat */
124 if (aim != NULL && *aim != '\0') {
125 _M_ADD_R("AIM", aim)
127 if (data->msg != NULL && *data->msg != '\0') {
128 _M_ADD_R("msg", data->msg)
130 if (data->phsh != NULL && *data->phsh != '\0') {
131 _M_ADD_R("phsh", data->phsh)
134 /* TODO: ext, nick */
135 return ret;
138 static void free_presence_txt_records(GSList *lst) {
139 PurpleKeyValuePair *kvp;
140 while(lst) {
141 kvp = lst->data;
142 g_free(kvp->key);
143 g_free(kvp->value);
144 g_free(kvp);
145 lst = g_slist_remove(lst, lst->data);
149 static gboolean publish_presence(BonjourDnsSd *data, PublishType type) {
150 GSList *txt_records;
151 gboolean ret;
153 txt_records = generate_presence_txt_records(data);
154 ret = _mdns_publish(data, type, txt_records);
155 free_presence_txt_records(txt_records);
157 return ret;
161 * Send a new dns-sd packet updating our status.
163 void bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message) {
164 g_free(data->status);
165 g_free(data->msg);
167 data->status = g_strdup(status);
168 data->msg = g_strdup(status_message);
170 /* Update our text record with the new status */
171 publish_presence(data, PUBLISH_UPDATE);
175 * Retrieve the buddy icon blob
177 void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
178 _mdns_retrieve_buddy_icon(buddy);
181 void bonjour_dns_sd_update_buddy_icon(BonjourDnsSd *data) {
182 PurpleStoredImage *img;
184 if ((img = purple_buddy_icons_find_account_icon(data->account))) {
185 gconstpointer avatar_data;
186 gsize avatar_len;
188 avatar_data = purple_imgstore_get_data(img);
189 avatar_len = purple_imgstore_get_size(img);
191 if (_mdns_set_buddy_icon_data(data, avatar_data, avatar_len)) {
192 /* The filename is a SHA-1 hash of the data (conveniently what we need) */
193 const char *p, *filename = purple_imgstore_get_filename(img);
195 g_free(data->phsh);
196 data->phsh = NULL;
198 /* Get rid of the extension */
199 p = strchr(filename, '.');
200 if (p)
201 data->phsh = g_strndup(filename, p - filename);
202 else
203 purple_debug_error("bonjour", "account buddy icon returned unexpected filename (%s)"
204 "; unable to extract hash. Clearing buddy icon\n", filename);
206 /* Update our TXT record */
207 publish_presence(data, PUBLISH_UPDATE);
210 purple_imgstore_unref(img);
211 } else {
212 /* We need to do this regardless of whether data->phsh is set so that we
213 * cancel any icons that are currently in the process of being set */
214 _mdns_set_buddy_icon_data(data, NULL, 0);
215 if (data->phsh != NULL) {
216 /* Clear the buddy icon */
217 g_free(data->phsh);
218 data->phsh = NULL;
219 /* Update our TXT record */
220 publish_presence(data, PUBLISH_UPDATE);
226 * Advertise our presence within the dns-sd daemon and start browsing
227 * for other bonjour peers.
229 gboolean bonjour_dns_sd_start(BonjourDnsSd *data) {
231 /* Initialize the dns-sd data and session */
232 if (!_mdns_init_session(data))
233 return FALSE;
235 /* Publish our bonjour IM client at the mDNS daemon */
236 if (!publish_presence(data, PUBLISH_START))
237 return FALSE;
239 /* Advise the daemon that we are waiting for connections */
240 if (!_mdns_browse(data)) {
241 purple_debug_error("bonjour", "Unable to get service.\n");
242 return FALSE;
245 return TRUE;
249 * Unregister the "_presence._tcp" service at the mDNS daemon.
252 void bonjour_dns_sd_stop(BonjourDnsSd *data) {
253 _mdns_stop(data);