pidgin/pidgin

b5eccef8404e
Avoid manual handling of icons in request field list items

* Use `PurpleKeyValuePair` as field list items
* Replace `icons` list with `has_icons` flag, thus fixing memleak
* Replace `purple_request_field_list_get_icons` with `purple_request_field_list_has_icons`

Testing Done:
Compile and run.

Reviewed at https://reviews.imfreedom.org/r/563/
/*
* Purple - Internet Messaging Library
* Copyright (C) Pidgin Developers <devel@pidgin.im>
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <glib/gi18n-lib.h>
#include "zephyr_tzc.h"
#define MAXCHILDREN 20
typedef gssize (*PollableInputStreamReadFunc)(GPollableInputStream *stream, void *bufcur, GError **error);
static gboolean tzc_write(zephyr_account *zephyr, const gchar *format, ...) G_GNUC_PRINTF(2, 3);
static gchar *
tzc_read(zephyr_account *zephyr, PollableInputStreamReadFunc read_func)
{
GPollableInputStream *stream = G_POLLABLE_INPUT_STREAM(zephyr->tzc_stdout);
gsize bufsize = 2048;
gchar *buf = g_new(gchar, bufsize);
gchar *bufcur = buf;
gboolean selected = FALSE;
while (TRUE) {
GError *error = NULL;
if (read_func(stream, bufcur, &error) < 0) {
if (error->code == G_IO_ERROR_WOULD_BLOCK ||
error->code == G_IO_ERROR_TIMED_OUT) {
g_error_free(error);
break;
}
purple_debug_error("zephyr", "couldn't read: %s", error->message);
purple_connection_error(purple_account_get_connection(zephyr->account), PURPLE_CONNECTION_ERROR_NETWORK_ERROR, "couldn't read");
g_error_free(error);
g_free(buf);
return NULL;
}
selected = TRUE;
bufcur++;
if ((bufcur - buf) > (bufsize - 1)) {
if ((buf = g_realloc(buf, bufsize * 2)) == NULL) {
purple_debug_error("zephyr","Ran out of memory\n");
exit(-1);
} else {
bufcur = buf + bufsize;
bufsize *= 2;
}
}
}
*bufcur = '\0';
if (!selected) {
g_free(buf);
buf = NULL;
}
return buf;
}
static gboolean
tzc_write(zephyr_account *zephyr, const gchar *format, ...)
{
va_list args;
gchar *message;
GError *error = NULL;
gboolean success;
va_start(args, format);
message = g_strdup_vprintf(format, args);
va_end(args);
success = g_output_stream_write_all(zephyr->tzc_stdin, message, strlen(message),
NULL, NULL, &error);
if (!success) {
purple_debug_error("zephyr", "Unable to write a message: %s", error->message);
g_error_free(error);
}
g_free(message);
return success;
}
/* Munge the outgoing zephyr so that any quotes or backslashes are
escaped and do not confuse tzc: */
static char *
tzc_escape_msg(const char *message)
{
gsize msglen;
char *newmsg;
if (!message || !*message) {
return g_strdup("");
}
msglen = strlen(message);
newmsg = g_new0(char, msglen*2 + 1);
for (gsize pos = 0, pos2 = 0; pos < msglen; pos++, pos2++) {
if (message[pos] == '\\' || message[pos] == '"') {
newmsg[pos2] = '\\';
pos2++;
}
newmsg[pos2] = message[pos];
}
return newmsg;
}
static char *
tzc_deescape_str(const char *message)
{
gsize msglen;
char *newmsg;
if (!message || !*message) {
return g_strdup("");
}
msglen = strlen(message);
newmsg = g_new0(char, msglen + 1);
for (gsize pos = 0, pos2 = 0; pos < msglen; pos++, pos2++) {
if (message[pos] == '\\') {
pos++;
}
newmsg[pos2] = message[pos];
}
return newmsg;
}
static GSubprocess *
get_tzc_process(const zephyr_account *zephyr)
{
GSubprocess *tzc_process = NULL;
const gchar *tzc_cmd;
gchar **tzc_cmd_array = NULL;
GError *error = NULL;
gboolean found_ps = FALSE;
gint i;
/* tzc_command should really be of the form
path/to/tzc -e %s
or
ssh username@hostname pathtotzc -e %s
-- this should not require a password, and ideally should be
kerberized ssh --
or
fsh username@hostname pathtotzc -e %s
*/
tzc_cmd = purple_account_get_string(zephyr->account, "tzc_command", "/usr/bin/tzc -e %s");
if (!g_shell_parse_argv(tzc_cmd, NULL, &tzc_cmd_array, &error)) {
purple_debug_error("zephyr", "Unable to parse tzc_command: %s", error->message);
purple_connection_error(
purple_account_get_connection(zephyr->account),
PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
"invalid tzc_command setting");
g_error_free(error);
return NULL;
}
for (i = 0; tzc_cmd_array[i] != NULL; i++) {
if (purple_strequal(tzc_cmd_array[i], "%s")) {
g_free(tzc_cmd_array[i]);
tzc_cmd_array[i] = g_strdup(zephyr->exposure);
found_ps = TRUE;
}
}
if (!found_ps) {
purple_debug_error("zephyr", "tzc exited early");
purple_connection_error(
purple_account_get_connection(zephyr->account),
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
"invalid output by tzc (or bad parsing code)");
g_strfreev(tzc_cmd_array);
return NULL;
}
tzc_process = g_subprocess_newv(
(const gchar *const *)tzc_cmd_array,
G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE,
&error);
if (tzc_process == NULL) {
purple_debug_error("zephyr", "tzc exited early: %s", error->message);
purple_connection_error(
purple_account_get_connection(zephyr->account),
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
"invalid output by tzc (or bad parsing code)");
g_error_free(error);
}
g_strfreev(tzc_cmd_array);
return tzc_process;
}
static gssize
pollable_input_stream_read(GPollableInputStream *stream, void *bufcur, GError **error)
{
return g_pollable_input_stream_read_nonblocking(stream, bufcur, 1, NULL, error);
}
static gssize
pollable_input_stream_read_with_timeout(GPollableInputStream *stream,
void *bufcur, GError **error)
{
const gint64 timeout = 10 * G_USEC_PER_SEC;
gint64 now = g_get_monotonic_time();
while (g_get_monotonic_time() < now + timeout) {
GError *local_error = NULL;
gssize ret = g_pollable_input_stream_read_nonblocking(
stream, bufcur, 1, NULL, &local_error);
if (ret == 1) {
return ret;
}
if (local_error->code != G_IO_ERROR_WOULD_BLOCK) {
g_propagate_error(error, local_error);
return ret;
}
/* Keep on waiting if this is a blocking error. */
g_clear_error(&local_error);
}
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
"tzc did not respond in time");
return -1;
}
static gint
get_paren_level(gint paren_level, gchar ch)
{
switch (ch) {
case '(': return paren_level + 1;
case ')': return paren_level - 1;
default: return paren_level;
}
}
static void
parse_tzc_login_data(zephyr_account *zephyr, const gchar *buf, gint buflen)
{
gchar *str = g_strndup(buf, buflen);
purple_debug_info("zephyr", "tempstr parsed");
/* str should now be a string containing all characters from
* buf after the first ( to the one before the last paren ).
* We should have the following possible lisp strings but we don't care
* (tzcspew . start) (version . "something") (pid . number)
* We care about 'zephyrid . "username@REALM.NAME"' and
* 'exposure . "SOMETHING"' */
if (!g_ascii_strncasecmp(str, "zephyrid", 8)) {
gchar **strv;
gchar *username;
const char *at;
purple_debug_info("zephyr", "zephyrid found");
strv = g_strsplit(str + 8, "\"", -1);
username = strv[1] ? strv[1] : "";
zephyr->username = g_strdup(username);
at = strchr(username, '@');
if (at != NULL) {
zephyr->realm = g_strdup(at + 1);
} else {
zephyr->realm = get_zephyr_realm(zephyr->account, "local-realm");
}
g_strfreev(strv);
} else {
purple_debug_info("zephyr", "something that's not zephyr id found %s", str);
}
/* We don't care about anything else yet */
g_free(str);
}
static gchar *
tree_child_contents(GNode *tree, int index)
{
GNode *child = g_node_nth_child(tree, index);
return child ? child->data : "";
}
static GNode *
find_node(GNode *ptree, gchar *key)
{
guint num_children;
gchar* tc;
if (!ptree || ! key)
return NULL;
num_children = g_node_n_children(ptree);
tc = tree_child_contents(ptree, 0);
/* g_strcasecmp() is deprecated. What is the encoding here??? */
if (num_children > 0 && tc && !g_ascii_strcasecmp(tc, key)) {
return ptree;
} else {
GNode *result = NULL;
guint i;
for (i = 0; i < num_children; i++) {
result = find_node(g_node_nth_child(ptree, i), key);
if(result != NULL) {
break;
}
}
return result;
}
}
static GNode *
parse_buffer(const gchar *source, gboolean do_parse)
{
GNode *ptree = g_node_new(NULL);
if (do_parse) {
unsigned int p = 0;
while(p < strlen(source)) {
unsigned int end;
gchar *newstr;
/* Eat white space: */
if(g_ascii_isspace(source[p]) || source[p] == '\001') {
p++;
continue;
}
/* Skip comments */
if(source[p] == ';') {
while(source[p] != '\n' && p < strlen(source)) {
p++;
}
continue;
}
if(source[p] == '(') {
int nesting = 0;
gboolean in_quote = FALSE;
gboolean escape_next = FALSE;
p++;
end = p;
while(!(source[end] == ')' && nesting == 0 && !in_quote) && end < strlen(source)) {
if(!escape_next) {
if(source[end] == '\\') {
escape_next = TRUE;
}
if(!in_quote) {
nesting = get_paren_level(nesting, source[end]);
}
if(source[end] == '"') {
in_quote = !in_quote;
}
} else {
escape_next = FALSE;
}
end++;
}
do_parse = TRUE;
} else {
gchar end_char;
if(source[p] == '"') {
end_char = '"';
p++;
} else {
end_char = ' ';
}
do_parse = FALSE;
end = p;
while(source[end] != end_char && end < strlen(source)) {
if(source[end] == '\\')
end++;
end++;
}
}
newstr = g_new0(gchar, end+1-p);
strncpy(newstr,source+p,end-p);
if (g_node_n_children(ptree) < MAXCHILDREN) {
/* In case we surpass maxchildren, ignore this */
g_node_append(ptree, parse_buffer(newstr, do_parse));
} else {
purple_debug_error("zephyr","too many children in tzc output. skipping\n");
}
g_free(newstr);
p = end + 1;
}
} else {
/* XXX does this have to be strdup'd */
ptree->data = g_strdup(source);
}
return ptree;
}
gboolean
tzc_login(zephyr_account *zephyr)
{
gchar *buf = NULL;
const gchar *bufend = NULL;
const gchar *ptr;
const gchar *tmp;
gint parenlevel = 0;
zephyr->tzc_proc = get_tzc_process(zephyr);
if (zephyr->tzc_proc == NULL) {
return FALSE;
}
zephyr->tzc_stdin = g_subprocess_get_stdin_pipe(zephyr->tzc_proc);
zephyr->tzc_stdout = g_subprocess_get_stdout_pipe(zephyr->tzc_proc);
purple_debug_info("zephyr", "about to read from tzc");
buf = tzc_read(zephyr, pollable_input_stream_read_with_timeout);
if (buf == NULL) {
return FALSE;
}
bufend = buf + strlen(buf);
ptr = buf;
/* ignore all tzcoutput till we've received the first ( */
while (ptr < bufend && (*ptr != '(')) {
ptr++;
}
if (ptr >= bufend) {
purple_connection_error(
purple_account_get_connection(zephyr->account),
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
"invalid output by tzc (or bad parsing code)");
g_free(buf);
return FALSE;
}
do {
parenlevel = get_paren_level(parenlevel, *ptr);
purple_debug_info("zephyr", "tzc parenlevel is %d", parenlevel);
switch (parenlevel) {
case 1:
/* Search for next beginning (, or for the ending */
do {
ptr++;
} while ((ptr < bufend) && (*ptr != '(') && (*ptr != ')'));
if (ptr >= bufend) {
purple_debug_error("zephyr", "tzc parsing error");
}
break;
case 2:
/* You are probably at
(foo . bar ) or (foo . "bar") or (foo . chars) or (foo . numbers) or (foo . () )
Parse all the data between the first and last f, and move past )
*/
tmp = ptr;
do {
ptr++;
parenlevel = get_paren_level(parenlevel, *ptr);
} while (parenlevel > 1);
parse_tzc_login_data(zephyr, tmp + 1, ptr - tmp);
ptr++;
break;
default:
purple_debug_info("zephyr", "parenlevel is not 1 or 2");
/* This shouldn't be happening */
break;
}
} while (ptr < bufend && parenlevel != 0);
purple_debug_info("zephyr", "tzc startup done");
g_free(buf);
return TRUE;
}
gint
tzc_check_notify(gpointer data)
{
PurpleConnection *gc = (PurpleConnection *)data;
zephyr_account* zephyr = purple_connection_get_protocol_data(gc);
GNode *newparsetree = NULL;
gchar *buf = tzc_read(zephyr, pollable_input_stream_read);
if (buf != NULL) {
newparsetree = parse_buffer(buf, TRUE);
g_free(buf);
}
if (newparsetree != NULL) {
gchar *spewtype;
if ( (spewtype = tree_child_contents(find_node(newparsetree, "tzcspew"), 2)) ) {
if (!g_ascii_strncasecmp(spewtype,"message",7)) {
ZNotice_t notice;
GNode *msgnode = g_node_nth_child(find_node(newparsetree, "message"), 2);
/*char *zsig = g_strdup(" ");*/ /* purple doesn't care about zsigs */
char *msg = tzc_deescape_str(tree_child_contents(msgnode, 1));
char *buf = g_strdup_printf(" %c%s", '\0', msg);
memset((char *)&notice, 0, sizeof(notice));
notice.z_kind = ACKED;
notice.z_port = 0;
notice.z_opcode = tree_child_contents(find_node(newparsetree, "opcode"), 2);
notice.z_class = tzc_deescape_str(tree_child_contents(find_node(newparsetree, "class"), 2));
notice.z_class_inst = tree_child_contents(find_node(newparsetree, "instance"), 2);
notice.z_recipient = zephyr_normalize_local_realm(zephyr, tree_child_contents(find_node(newparsetree, "recipient"), 2));
notice.z_sender = zephyr_normalize_local_realm(zephyr, tree_child_contents(find_node(newparsetree, "sender"), 2));
notice.z_default_format = "Class $class, Instance $instance:\n" "To: @bold($recipient) at $time $date\n" "From: @bold($1) <$sender>\n\n$2";
notice.z_message_len = 1 + 1 + strlen(msg) + 1;
notice.z_message = buf;
handle_message(gc, &notice);
g_free(msg);
/*g_free(zsig);*/
g_free(buf);
}
else if (!g_ascii_strncasecmp(spewtype,"zlocation",9)) {
/* check_loc or zephyr_zloc respectively */
/* XXX fix */
GNode *locations = g_node_nth_child(g_node_nth_child(find_node(newparsetree, "locations"), 2), 0);
ZLocations_t zloc = {
.host = tree_child_contents(g_node_nth_child(locations, 0), 2),
.time = tree_child_contents(g_node_nth_child(locations, 2), 2),
.tty = NULL
};
handle_locations(gc, tree_child_contents(find_node(newparsetree, "user"), 2),
(zloc.host && *zloc.host && !purple_strequal(zloc.host, " ")) ? 1 : 0,
&zloc);
}
else if (!g_ascii_strncasecmp(spewtype,"subscribed",10)) {
}
else if (!g_ascii_strncasecmp(spewtype,"start",5)) {
}
else if (!g_ascii_strncasecmp(spewtype,"error",5)) {
/* XXX handle */
}
} else {
}
} else {
}
g_node_destroy(newparsetree);
return TRUE;
}
gboolean
tzc_subscribe_to(zephyr_account *zephyr, ZSubscription_t *sub)
{
/* ((tzcfodder . subscribe) ("class" "instance" "recipient")) */
return tzc_write(zephyr, "((tzcfodder . subscribe) (\"%s\" \"%s\" \"%s\"))\n",
sub->zsub_class, sub->zsub_classinst, sub->zsub_recipient);
}
gboolean
tzc_request_locations(zephyr_account *zephyr, gchar *who)
{
return tzc_write(zephyr, "((tzcfodder . zlocate) \"%s\")\n", who);
}
gboolean
tzc_send_message(zephyr_account *zephyr, gchar *zclass, gchar *instance, gchar *recipient,
const gchar *html_buf, const gchar *sig, G_GNUC_UNUSED const gchar *opcode)
{
/* CMU cclub tzc doesn't grok opcodes for now */
char *tzc_sig = tzc_escape_msg(sig);
char *tzc_body = tzc_escape_msg(html_buf);
gboolean result;
result = tzc_write(zephyr, "((tzcfodder . send) (class . \"%s\") (auth . t) (recipients (\"%s\" . \"%s\")) (message . (\"%s\" \"%s\")) ) \n",
zclass, instance, recipient, tzc_sig, tzc_body);
g_free(tzc_sig);
g_free(tzc_body);
return result;
}
void
tzc_set_location(zephyr_account *zephyr, char *exposure)
{
tzc_write(zephyr, "((tzcfodder . set-location) (hostname . \"%s\") (exposure . \"%s\"))\n",
zephyr->ourhost, exposure);
}
void
tzc_get_subs_from_server(G_GNUC_UNUSED zephyr_account *zephyr, PurpleConnection *gc)
{
/* XXX fix */
purple_notify_error(gc, "", "tzc doesn't support this action",
NULL, purple_request_cpar_from_connection(gc));
}
void
tzc_close(zephyr_account *zephyr)
{
#ifdef G_OS_UNIX
GError *error = NULL;
g_subprocess_send_signal(zephyr->tzc_proc, SIGTERM);
if (!g_subprocess_wait(zephyr->tzc_proc, NULL, &error)) {
purple_debug_error("zephyr",
"error while attempting to close tzc: %s",
error->message);
g_error_free(error);
}
#else
g_subprocess_force_exit(zephyr->tzc_proc);
#endif
zephyr->tzc_stdin = NULL;
zephyr->tzc_stdout = NULL;
g_clear_object(&zephyr->tzc_proc);
}