Beispiel #1
0
void irc_msg_default(struct irc_conn *irc, const char *name, const char *from, char **args)
{
	char *clean;
	/* This, too, should be escaped somehow (smarter) */
	clean = purple_utf8_salvage(args[0]);
	purple_debug(PURPLE_DEBUG_INFO, "irc", "Unrecognized message: %s\n", clean);
	g_free(clean);
}
Beispiel #2
0
static void irc_parse_error_cb(struct irc_conn *irc, char *input)
{
	char *clean;
	/* This really should be escaped somehow that you can tell what
	 * the junk was -- but as it is, it can crash glib. */
	clean = purple_utf8_salvage(input);
	purple_debug(PURPLE_DEBUG_WARNING, "irc", "Unrecognized string: %s\n", clean);
	g_free(clean);
}
Beispiel #3
0
static char *irc_recv_convert(struct irc_conn *irc, const char *string)
{
	char *utf8 = NULL;
	const gchar *charset, *enclist;
	gchar **encodings;
	gboolean autodetect;
	int i;

	enclist = purple_account_get_string(irc->account, "encoding", IRC_DEFAULT_CHARSET);
	encodings = g_strsplit(enclist, ",", -1);

	if (encodings[0] == NULL) {
		g_strfreev(encodings);
		return purple_utf8_salvage(string);
	}

	autodetect = purple_account_get_bool(irc->account, "autodetect_utf8", IRC_DEFAULT_AUTODETECT);

	if (autodetect && g_utf8_validate(string, -1, NULL)) {
		return g_strdup(string);
	}

	for (i = 0; encodings[i] != NULL; i++) {
		charset = encodings[i];
		while (*charset == ' ')
			charset++;

		if (!g_ascii_strcasecmp("UTF-8", charset)) {
			if (g_utf8_validate(string, -1, NULL))
				utf8 = g_strdup(string);
		} else {
			utf8 = g_convert(string, -1, "UTF-8", charset, NULL, NULL, NULL);
		}

		if (utf8) {
			g_strfreev(encodings);
			return utf8;
		}
	}
	g_strfreev(encodings);

	return purple_utf8_salvage(string);
}
Beispiel #4
0
int irc_send_len(struct irc_conn *irc, const char *buf, int buflen)
{
	int ret;
 	char *tosend = g_strdup(buf);

	purple_signal_emit(_irc_plugin, "irc-sending-text", purple_account_get_connection(irc->account), &tosend);

	if (tosend == NULL)
		return 0;

	if (!purple_strequal(tosend, buf)) {
		buflen = strlen(tosend);
	}

	if (purple_debug_is_verbose()) {
		char *clean = purple_utf8_salvage(tosend);
		clean = g_strstrip(clean);
		purple_debug_misc("irc", "<< %s\n", clean);
		g_free(clean);
	}

	/* If we're not buffering writes, try to send immediately */
	if (!irc->writeh)
		ret = do_send(irc, tosend, buflen);
	else {
		ret = -1;
		errno = EAGAIN;
	}

	/* purple_debug(PURPLE_DEBUG_MISC, "irc", "sent%s: %s",
		irc->gsc ? " (ssl)" : "", tosend); */
	if (ret <= 0 && errno != EAGAIN) {
		PurpleConnection *gc = purple_account_get_connection(irc->account);
		gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
			g_strerror(errno));
		purple_connection_error_reason (gc,
			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
		g_free(tmp);
	} else if (ret < buflen) {
		if (ret < 0)
			ret = 0;
		if (!irc->writeh)
			irc->writeh = purple_input_add(
				irc->gsc ? irc->gsc->fd : irc->fd,
				PURPLE_INPUT_WRITE, irc_send_cb, irc);
		purple_circ_buffer_append(irc->outbuf, tosend + ret,
			buflen - ret);
	}
	g_free(tosend);
	return ret;
}
Beispiel #5
0
void
msn_message_parse_payload(MsnMessage *msg,
						  const char *payload, size_t payload_len,
						  const char *line_dem,const char *body_dem)
{
	char *tmp_base, *tmp;
	const char *content_type;
	char *end;
	char **elems, **cur, **tokens;

	g_return_if_fail(payload != NULL);
	tmp_base = tmp = g_malloc(payload_len + 1);
	memcpy(tmp_base, payload, payload_len);
	tmp_base[payload_len] = '\0';

	/* Find the end of the headers */
	end = strstr(tmp, body_dem);
	/* TODO? some clients use \r delimiters instead of \r\n, the official client
	 * doesn't send such messages, but does handle receiving them. We'll just
	 * avoid crashing for now */
	if (end == NULL) {
		g_free(tmp_base);
		g_return_if_reached();
	}

	/* NUL-terminate the end of the headers - it'll get skipped over below */
	*end = '\0';

	/* Split the headers and parse each one */
	elems = g_strsplit(tmp, line_dem, 0);
	for (cur = elems; *cur != NULL; cur++)
	{
		const char *key, *value;

		/* If this line starts with whitespace, it's been folded from the
		   previous line and won't have ':'. */
		if ((**cur == ' ') || (**cur == '\t')) {
			tokens = g_strsplit(g_strchug(*cur), "=\"", 2);
			key = tokens[0];
			value = tokens[1];

			/* The only one I care about is 'boundary' (which is folded from
			   the key 'Content-Type'), so only process that. */
			if (!strcmp(key, "boundary") && value) {
				char *end = strchr(value, '\"');
				if (end) {
					*end = '\0';
					msn_message_set_header(msg, key, value);
				}
			}

			g_strfreev(tokens);
			continue;
		}

		tokens = g_strsplit(*cur, ": ", 2);

		key = tokens[0];
		value = tokens[1];

		if (!strcmp(key, "MIME-Version"))
		{
			/* Ignore MIME-Version header */
		}
		else if (!strcmp(key, "Content-Type"))
		{
			char *charset, *c;

			if (value && (c = strchr(value, ';')) != NULL)
			{
				if ((charset = strchr(c, '=')) != NULL)
				{
					charset++;
					msn_message_set_charset(msg, charset);
				}

				*c = '\0';
			}

			msn_message_set_content_type(msg, value);
		}
		else
		{
			msn_message_set_header(msg, key, value);
		}

		g_strfreev(tokens);
	}
	g_strfreev(elems);

	/* Proceed to the end of the "\r\n\r\n" */
	tmp = end + strlen(body_dem);

	/* Now we *should* be at the body. */
	content_type = msn_message_get_content_type(msg);

	if (payload_len - (tmp - tmp_base) > 0) {
		msg->body_len = payload_len - (tmp - tmp_base);
		g_free(msg->body);
		msg->body = g_malloc(msg->body_len + 1);
		memcpy(msg->body, tmp, msg->body_len);
		msg->body[msg->body_len] = '\0';
	}

	if (msg->body && content_type && purple_str_has_prefix(content_type, "text/")) {
		char *body = NULL;

		if (msg->charset == NULL || g_str_equal(msg->charset, "UTF-8")) {
			/* Charset is UTF-8 */
			if (!g_utf8_validate(msg->body, msg->body_len, NULL)) {
				purple_debug_warning("msn", "Message contains invalid "
						"UTF-8. Attempting to salvage.\n");
				body = purple_utf8_salvage(msg->body);
				payload_len = strlen(body);
			}
		} else {
			/* Charset is something other than UTF-8 */
			GError *err = NULL;
			body = g_convert(msg->body, msg->body_len, "UTF-8",
					msg->charset, NULL, &payload_len, &err);
			if (!body || err) {
				purple_debug_warning("msn", "Unable to convert message from "
						"%s to UTF-8: %s\n", msg->charset,
						err ? err->message : "Unknown error");
				if (err)
					g_error_free(err);

				/* Fallback to ISO-8859-1 */
				g_free(body);
				body = g_convert(msg->body, msg->body_len, "UTF-8",
						"ISO-8859-1", NULL, &payload_len, NULL);
				if (!body) {
					g_free(msg->body);
					msg->body = NULL;
					msg->body_len = 0;
				}
			}
		}

		if (body) {
			g_free(msg->body);
			msg->body = body;
			msg->body_len = payload_len;
			msn_message_set_charset(msg, "UTF-8");
		}
	}

	g_free(tmp_base);
}
Beispiel #6
0
/**
 * Someone else wants to establish a peer connection with us.
 */
void
peer_connection_got_proposition(OscarData *od, const gchar *bn, const gchar *message, IcbmArgsCh2 *args)
{
	PurpleConnection *gc;
	PurpleAccount *account;
	PeerConnection *conn;
	gchar *buf;

	gc = od->gc;
	account = purple_connection_get_account(gc);

	/*
	 * If we have a connection with this same cookie then they are
	 * probably just telling us they weren't able to connect to us
	 * and we should try connecting to them, instead.  Or they want
	 * to go through a proxy.
	 */
	conn = peer_connection_find_by_cookie(od, bn, args->cookie);
	if ((conn != NULL) && (conn->type == args->type))
	{
		purple_debug_info("oscar", "Remote user wants to try a "
				"different connection method\n");
		g_free(conn->proxyip);
		g_free(conn->clientip);
		g_free(conn->verifiedip);
		if (args->use_proxy)
			conn->proxyip = g_strdup(args->proxyip);
		else
			conn->proxyip = NULL;
		conn->verifiedip = g_strdup(args->verifiedip);
		conn->clientip = g_strdup(args->clientip);
		conn->port = args->port;
		conn->use_proxy |= args->use_proxy;
		conn->lastrequestnumber++;
		peer_connection_trynext(conn);
		return;
	}

	/* If this is a direct IM, then close any existing session */
	if (args->type == OSCAR_CAPABILITY_DIRECTIM)
	{
		conn = peer_connection_find_by_type(od, bn, args->type);
		if (conn != NULL)
		{
			/* Close the old direct IM and start a new one */
			purple_debug_info("oscar", "Received new direct IM request "
				"from %s.  Destroying old connection.\n", bn);
			peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED, NULL);
		}
	}

	/* Check for proper arguments */
	if (args->type == OSCAR_CAPABILITY_SENDFILE)
	{
		if ((args->info.sendfile.filename == NULL) ||
			(args->info.sendfile.totsize == 0) ||
			(args->info.sendfile.totfiles == 0))
		{
			purple_debug_warning("oscar",
					"%s tried to send you a file with incomplete "
					"information.\n", bn);
			return;
		}
	}

	conn = peer_connection_new(od, args->type, bn);
	memcpy(conn->cookie, args->cookie, 8);
	if (args->use_proxy)
		conn->proxyip = g_strdup(args->proxyip);
	conn->clientip = g_strdup(args->clientip);
	conn->verifiedip = g_strdup(args->verifiedip);
	conn->port = args->port;
	conn->use_proxy |= args->use_proxy;
	conn->lastrequestnumber++;

	if (args->type == OSCAR_CAPABILITY_DIRECTIM)
	{
		buf = g_strdup_printf(_("%s has just asked to directly connect to %s"),
				bn, purple_account_get_username(account));

		purple_request_action(conn, NULL, buf,
						_("This requires a direct connection between "
						  "the two computers and is necessary for IM "
						  "Images.  Because your IP address will be "
						  "revealed, this may be considered a privacy "
						  "risk."),
						PURPLE_DEFAULT_ACTION_NONE,
						account, bn, NULL,
						conn, 2,
						_("C_onnect"), G_CALLBACK(peer_connection_got_proposition_yes_cb),
						_("Cancel"), G_CALLBACK(peer_connection_got_proposition_no_cb));
	}
	else if (args->type == OSCAR_CAPABILITY_SENDFILE)
	{
		gchar *filename;

		conn->xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE, bn);
		if (conn->xfer)
		{
			conn->xfer->data = conn;
			purple_xfer_ref(conn->xfer);
			purple_xfer_set_size(conn->xfer, args->info.sendfile.totsize);

			/* Set the file name */
			if (g_utf8_validate(args->info.sendfile.filename, -1, NULL))
				filename = g_strdup(args->info.sendfile.filename);
			else
				filename = purple_utf8_salvage(args->info.sendfile.filename);

			if (args->info.sendfile.subtype == AIM_OFT_SUBTYPE_SEND_DIR)
			{
				/*
				 * If they are sending us a directory then the last character
				 * of the file name will be an asterisk.  We don't want to
				 * save stuff to a directory named "*" so we remove the
				 * asterisk from the file name.
				 */
				char *tmp = strrchr(filename, '\\');
				if ((tmp != NULL) && (tmp[1] == '*'))
					tmp[0] = '\0';
			}
			purple_xfer_set_filename(conn->xfer, filename);
			g_free(filename);

			/*
			 * Set the message, unless this is the dummy message from an
			 * ICQ client or an empty message from an AIM client.
			 * TODO: Maybe we should strip HTML and then see if strlen>0?
			 */
			if ((message != NULL) &&
				(g_ascii_strncasecmp(message, "<ICQ_COOL_FT>", 13) != 0) &&
				(g_ascii_strcasecmp(message, "<HTML>") != 0))
			{
				purple_xfer_set_message(conn->xfer, message);
			}

			/* Setup our I/O op functions */
			purple_xfer_set_init_fnc(conn->xfer, peer_oft_recvcb_init);
			purple_xfer_set_end_fnc(conn->xfer, peer_oft_recvcb_end);
			purple_xfer_set_request_denied_fnc(conn->xfer, peer_oft_cb_generic_cancel);
			purple_xfer_set_cancel_recv_fnc(conn->xfer, peer_oft_cb_generic_cancel);
			purple_xfer_set_ack_fnc(conn->xfer, peer_oft_recvcb_ack_recv);

			/* Now perform the request */
			purple_xfer_request(conn->xfer);
		}
	}
}
Beispiel #7
0
static void fb_got_notifications_cb(FacebookAccount *fba, gchar *url_text, gsize len, gpointer userdata)
{
	gchar *salvaged;
	time_t last_fetch_time;
	time_t time_of_message;
	time_t newest_message = 0;
	xmlnode *channel;//VOXOX - CJC - 2009.07.06 
	xmlnode *rss_root;//VOXOX - CJC - 2009.07.06 
	xmlnode *item;//VOXOX - CJC - 2009.07.06 
	xmlnode *link;//VOXOX - CJC - 2009.07.06 
	xmlnode *title;//VOXOX - CJC - 2009.07.06 
	gchar *tmp;
	gchar month_string[4], weekday[4];
	guint year, month, day, hour, minute, second;
	long timezone;
	gchar *subject, *url;

	month_string[3] = weekday[3] = '\0';
	year = month = day = hour = minute = second = 0;

	if (!url_text || !len)
		return;

	last_fetch_time = purple_account_get_int(fba->account, "facebook_notifications_last_fetch", 0);
	/* purple_debug_info("facebook", "last fetch time: %zu\n", last_fetch_time); */

	salvaged = purple_utf8_salvage(url_text);
	rss_root = xmlnode_from_str(salvaged, -1);
	g_free(salvaged);

	if (rss_root == NULL)
	{
		purple_debug_error("facebook", "Could not load RSS file\n");
		return;
	}
	channel = xmlnode_get_child(rss_root, "channel");
	if (channel == NULL)
	{
		purple_debug_warning("facebook", "Invalid RSS feed\n");
		xmlnode_free(rss_root);
		return;
	}
	item = xmlnode_get_child(channel, "item");
	if (item == NULL)
	{
		purple_debug_info("facebook", "No new notifications\n");
	}
	for (; item != NULL; item = xmlnode_get_next_twin(item))
	{
		xmlnode *pubDate = xmlnode_get_child(item, "pubDate");
		if (!pubDate)
			continue;
		tmp = xmlnode_get_data_unescaped(pubDate);
		/* rss times are in Thu, 19 Jun 2008 15:51:25 -1100 format */
		sscanf(tmp, "%3s, %2u %3s %4u %2u:%2u:%2u %5ld", (char*)&weekday, &day, (char*)&month_string, &year, &hour, &minute, &second, &timezone);
		if (g_str_equal(month_string, "Jan")) month = 0;
		else if (g_str_equal(month_string, "Feb")) month = 1;
		else if (g_str_equal(month_string, "Mar")) month = 2;
		else if (g_str_equal(month_string, "Apr")) month = 3;
		else if (g_str_equal(month_string, "May")) month = 4;
		else if (g_str_equal(month_string, "Jun")) month = 5;
		else if (g_str_equal(month_string, "Jul")) month = 6;
		else if (g_str_equal(month_string, "Aug")) month = 7;
		else if (g_str_equal(month_string, "Sep")) month = 8;
		else if (g_str_equal(month_string, "Oct")) month = 9;
		else if (g_str_equal(month_string, "Nov")) month = 10;
		else if (g_str_equal(month_string, "Dec")) month = 11;
		g_free(tmp);

		/* try using pidgin's functions */
		tmp = g_strdup_printf("%04u%02u%02uT%02u%02u%02u%05ld", year, month, day, hour, minute, second, timezone);
		time_of_message = purple_str_to_time(tmp, FALSE, NULL, NULL, NULL);
		g_free(tmp);

		if (time_of_message <= 0)
		{
			/* there's no cross-platform, portable way of converting string to time
			   which doesn't need a new version of glib, so just cheat */
			time_of_message = second + 60*minute + 3600*hour + 86400*day + 2592000*month + 31536000*(year-1970);
		}

		if (time_of_message > newest_message)
		{
			/* we'll keep the newest message to save */
			newest_message = time_of_message;
		}

		if (time_of_message <= last_fetch_time)
		{
			/* fortunatly, rss messages are ordered from newest to oldest */
			/* so if this message is older than the last one, ignore rest */
			break;
		}
		
		link = xmlnode_get_child(item, "link");
		if (link)
		{
			url = xmlnode_get_data_unescaped(link);
		} else {
			url = g_strdup("");
		}
		
		title = xmlnode_get_child(item, "title");
		if (title)
		{
			subject = xmlnode_get_data_unescaped(title);
		} else {
			subject = g_strdup("");
		}
		
		purple_notify_email(fba->pc, subject, NULL, fba->account->username, url, NULL, NULL);
		g_free(subject);
		g_free(url);
	}
	xmlnode_free(rss_root);

	if (newest_message > last_fetch_time)
	{
		/* update the last fetched time if we had newer messages */
		purple_account_set_int(fba->account, "facebook_notifications_last_fetch", newest_message);
	}
}
Beispiel #8
0
/*Post the Offline Instant Message to User Conversation*/
static void
msn_oim_report_to_user(MsnOimRecvData *rdata, const char *msg_str)
{
    MsnMessage *message;
    const char *date;
    const char *from;
    const char *boundary;
    char *decode_msg = NULL, *clean_msg = NULL;
    gsize body_len;
    char **tokens;
    char *passport = NULL;
    time_t stamp;
    const char *charset = NULL;

    message = msn_message_new(MSN_MSG_UNKNOWN);

    msn_message_parse_payload(message, msg_str, strlen(msg_str),
                              MSG_OIM_LINE_DEM, MSG_OIM_BODY_DEM);
    purple_debug_info("msn", "oim body:{%s}\n", message->body);

    boundary = msn_message_get_header_value(message, "boundary");

    if (boundary != NULL) {
        char *bounds;
        char **part;

        bounds = g_strdup_printf("--%s" MSG_OIM_LINE_DEM, boundary);
        tokens = g_strsplit(message->body, bounds, 0);

        /* tokens+1 to skip the "This is a multipart message..." text */
        for (part = tokens+1; *part != NULL; part++) {
            MsnMessage *multipart;
            const char *type;
            multipart = msn_message_new(MSN_MSG_UNKNOWN);
            msn_message_parse_payload(multipart, *part, strlen(*part),
                                      MSG_OIM_LINE_DEM, MSG_OIM_BODY_DEM);

            type = msn_message_get_content_type(multipart);
            if (type && !strcmp(type, "text/plain")) {
                decode_msg = (char *)purple_base64_decode(multipart->body, &body_len);
                charset = msn_message_get_charset(multipart);

                msn_message_unref(multipart);
                break;
            }
            msn_message_unref(multipart);
        }

        g_strfreev(tokens);
        g_free(bounds);

        if (decode_msg == NULL) {
            purple_debug_error("msn", "Couldn't find text/plain OIM message.\n");
            msn_message_unref(message);
            return;
        }
    } else {
        decode_msg = (char *)purple_base64_decode(message->body, &body_len);
        charset = msn_message_get_charset(message);
    }

    if (charset && !((g_ascii_strncasecmp(charset, "UTF-8", 5) == 0) || (g_ascii_strncasecmp(charset, "UTF8", 4) == 0))) {
        clean_msg = g_convert(decode_msg, body_len, "UTF-8", charset, NULL, NULL, NULL);

        if (!clean_msg) {
            char *clean = purple_utf8_salvage(decode_msg);

            purple_debug_error("msn", "Failed to convert charset from %s to UTF-8 for OIM message: %s\n", charset, clean);

            clean_msg = g_strdup_printf(_("%s (There was an error receiving this message. "
                                          "Converting the encoding from %s to UTF-8 failed.)"),
                                        clean, charset);
            g_free(clean);
        }

        g_free(decode_msg);

    } else if (!g_utf8_validate(decode_msg, body_len, NULL)) {
        char *clean = purple_utf8_salvage(decode_msg);

        purple_debug_error("msn", "Received an OIM message that is not UTF-8,"
                           " and no encoding specified: %s\n", clean);

        if (charset) {
            clean_msg = g_strdup_printf(_("%s (There was an error receiving this message."
                                          " The charset was %s, but it was not valid UTF-8.)"),
                                        clean, charset);
        } else {
            clean_msg = g_strdup_printf(_("%s (There was an error receiving this message."
                                          " The charset was missing, but it was not valid UTF-8.)"),
                                        clean);
        }

        g_free(clean);
        g_free(decode_msg);

    } else {
        clean_msg = decode_msg;
    }

    from = msn_message_get_header_value(message, "X-OIM-originatingSource");

    /* Match number to user's mobile number, FROM is a phone number
       if the other side pages you using your phone number */
    if (from && !strncmp(from, "tel:+", 5)) {
        MsnUser *user =	msn_userlist_find_user_with_mobile_phone(
                            rdata->oim->session->userlist, from + 4);

        if (user && user->passport)
            passport = g_strdup(user->passport);
    }

    if (passport == NULL) {
        char *start, *end;

        from = msn_message_get_header_value(message, "From");

        tokens = g_strsplit(from, " ", 2);
        if (tokens[1] != NULL)
            from = (const char *)tokens[1];

        start = strchr(from, '<');
        if (start != NULL) {
            start++;
            end = strchr(from, '>');
            if (end != NULL)
                passport = g_strndup(start, end - start);
        }
        if (passport == NULL)
            passport = g_strdup(_("Unknown"));

        g_strfreev(tokens);
    }

    date = msn_message_get_header_value(message, "Date");
    stamp = msn_oim_parse_timestamp(date);
    purple_debug_info("msn", "oim Date:{%s},passport{%s}\n",
                      date, passport);

    purple_serv_got_im(purple_account_get_connection(rdata->oim->session->account), passport, clean_msg, 0,
                       stamp);

    /*Now get the oim message ID from the oim_list.
     * and append to read list to prepare for deleting the Offline Message when sign out
     */
    msn_oim_post_delete_msg(rdata);

    g_free(passport);
    g_free(clean_msg);
    msn_message_unref(message);
}
Beispiel #9
0
static void purple_fbapi_request_fetch_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message)
{
	PurpleFbApiCall *apicall;
	xmlnode *response;
	PurpleConnectionError error = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
	char *error_message2 = NULL;

	apicall = user_data;

	if (error_message != NULL) {
		/* Request failed */

		if (apicall->attempt_number < MAX_CONNECTION_ATTEMPTS) {
			/* Retry! */
			apicall->url_data = purple_util_fetch_url_request(API_URL,
					TRUE, NULL, FALSE, apicall->request, FALSE,
					purple_fbapi_request_fetch_cb, apicall);
			apicall->attempt_number++;
			return;
		}

		response = NULL;
		error_message2 = g_strdup(error_message);
		error = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
	} else if (url_text != NULL && len > 0) {
		/* Parse the response as XML */
		response = xmlnode_from_str(url_text, len);

		if (response == NULL)
		{
			gchar *salvaged;

			if (g_utf8_validate(url_text, len, NULL)) {
				salvaged = g_strdup(url_text);
			} else {
				/* Facebook responded with invalid UTF-8.  Bastards. */
				purple_debug_error("fbapi", "Response is not valid UTF-8\n");
				salvaged = purple_utf8_salvage(url_text);
			}

			purple_fbapi_xml_salvage(salvaged);
			response = xmlnode_from_str(salvaged, -1);
			g_free(salvaged);
		}

		if (response == NULL) {
			purple_debug_error("fbapi", "Could not parse response as XML: %*s\n",
			(int)len, url_text);
			error_message2 = g_strdup(_("Invalid response from server"));
		} else if (g_str_equal(response->name, "error_response")) {
			/*
			 * The response is an error message, in the standard format
			 * for errors from API calls.
			 */
			xmlnode *tmp;
			char *tmpstr;

			tmp = xmlnode_get_child(response, "error_code");
			if (tmp != NULL) {
				tmpstr = xmlnode_get_data_unescaped(tmp);
				if (tmpstr != NULL && strcmp(tmpstr, "293") == 0) {
					error_message2 = g_strdup(_("Need chat permission"));
					error = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
				}
				g_free(tmpstr);
			}
			if (error_message2 == NULL) {
				error = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
				tmp = xmlnode_get_child(response, "error_msg");
				if (tmp != NULL)
					error_message2 = xmlnode_get_data_unescaped(tmp);
			}
			if (error_message2 == NULL)
				error_message2 = g_strdup(_("Unknown"));
		} else {
			error_message2 = NULL;
		}
	} else {
		/* Response body was empty */
		response = NULL;
		error_message2 = NULL;
	}

	if (apicall->attempt_number > 1 || error_message2 != NULL)
		purple_debug_error("fbapi", "Request '%s' %s after %u attempts: %s\n",
				apicall->request,
				error_message == NULL ? "succeeded" : "failed",
				apicall->attempt_number, error_message2);

	/*
	 * The request either succeeded or failed the maximum number of
	 * times.  In either case, pass control off to the callback
	 * function and let them decide what to do.
	 */
	apicall->callback(apicall, apicall->user_data, response, error, error_message2);
	apicall->url_data = NULL;
	purple_fbapi_request_destroy(apicall);

	xmlnode_free(response);
	g_free(error_message2);
}