Mon, 22 Aug 2022 21:40:04 -0500
Inline pidgin_make_scrollable
We need to change it for GTK4, and there are few enough that it can be inlined. Eventually, that code might be a `.ui` anyway.
Testing Done:
Compile only.
Reviewed at https://reviews.imfreedom.org/r/1615/
/* * 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 *)¬ice, 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, ¬ice); 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); }