Sat, 09 Aug 2025 17:37:27 +0800
Fix the birb header path
The birb header referred would only work with birb provided by wrap casuing
build to fail because of system-installed birb dependency. The commit points
it to the correct path <birb.h>.
See: https://keep.imfreedom.org/birb/birb/file/5bf00c7d7f80/birb/meson.build#l77
/* * 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 library 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 library 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 library; if not, see <https://www.gnu.org/licenses/>. */ #include <purpleconfig.h> #include <glib/gi18n-lib.h> #include <glib/gstdio.h> #include <ctype.h> #include "core.h" #include "debug.h" #include "prefs.h" #include "purpleaccountmanager.h" #include "purpleconversation.h" #include "purplepath.h" #include "purpleprotocol.h" #include "util.h" #ifdef _WIN32 # include "win32/libc_interface.h" #endif #include <json-glib/json-glib.h> #define BUF_LEN 2048 void purple_util_init(void) { } void purple_util_uninit(void) { purple_util_set_user_dir(NULL); } /************************************************************************** * Path/Filename Functions **************************************************************************/ static gboolean purple_util_write_data_to_file_common(const char *dir, const char *filename, const char *data, gssize size) { gchar *filename_full; gboolean ret = FALSE; g_return_val_if_fail(dir != NULL, FALSE); purple_debug_misc("util", "Writing file %s to directory %s", filename, dir); /* Ensure the directory exists */ if (!g_file_test(dir, G_FILE_TEST_IS_DIR)) { if (g_mkdir(dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1) { purple_debug_error("util", "Error creating directory %s: %s\n", dir, g_strerror(errno)); return FALSE; } } filename_full = g_build_filename(dir, filename, NULL); ret = g_file_set_contents(filename_full, data, size, NULL); g_free(filename_full); return ret; } gboolean purple_util_write_data_to_config_file(const char *filename, const char *data, gssize size) { const char *config_dir = purple_config_dir(); gboolean ret = purple_util_write_data_to_file_common(config_dir, filename, data, size); return ret; } PurpleXmlNode * purple_util_read_xml_from_config_file(const char *filename, const char *description) { return purple_xmlnode_from_file(purple_config_dir(), filename, description, "util"); } gboolean purple_running_gnome(void) { #ifndef _WIN32 gchar *tmp = g_find_program_in_path("gvfs-open"); if (tmp == NULL) { tmp = g_find_program_in_path("gnome-open"); if (tmp == NULL) { return FALSE; } } g_free(tmp); tmp = (gchar *)g_getenv("GNOME_DESKTOP_SESSION_ID"); return ((tmp != NULL) && (*tmp != '\0')); #else return FALSE; #endif } /************************************************************************** * String Functions **************************************************************************/ void purple_str_strip_char(char *text, char thechar) { int i, j; g_return_if_fail(text != NULL); for (i = 0, j = 0; text[i]; i++) if (text[i] != thechar) text[j++] = text[i]; text[j] = '\0'; } gchar * purple_strreplace(const char *string, const char *delimiter, const char *replacement) { gchar **split; gchar *ret; g_return_val_if_fail(string != NULL, NULL); g_return_val_if_fail(delimiter != NULL, NULL); g_return_val_if_fail(replacement != NULL, NULL); split = g_strsplit(string, delimiter, 0); ret = g_strjoinv(replacement, split); g_strfreev(split); return ret; } void purple_str_wipe(gchar *str) { if (str == NULL) return; memset(str, 0, strlen(str)); g_free(str); } gboolean purple_strmatches(const char *pattern, const char *str) { char *normal_pattern = NULL; char *normal_str = NULL; char *cmp_pattern = NULL; char *cmp_str = NULL; char *idx_pattern = NULL; char *idx_str = NULL; g_return_val_if_fail(pattern != NULL, FALSE); /* Short circuit on NULL and empty string. */ if(purple_strempty(str)) { return FALSE; } normal_pattern = g_utf8_normalize(pattern, -1, G_NORMALIZE_ALL); cmp_pattern = g_utf8_casefold(normal_pattern, -1); g_free(normal_pattern); normal_str = g_utf8_normalize(str, -1, G_NORMALIZE_ALL); cmp_str = g_utf8_casefold(normal_str, -1); g_free(normal_str); idx_pattern = cmp_pattern; idx_str = cmp_str; /* I know while(TRUE)'s suck, but the alternative would be a multi-line for * loop that wouldn't have the additional comments, which is much better * IMHO. -- GK 2023-01-24. */ while(TRUE) { gunichar character = g_utf8_get_char(idx_pattern); /* If we've reached the end of the pattern, we're done. */ if(character == 0) { break; } idx_str = g_utf8_strchr(idx_str, -1, character); if(idx_str == NULL) { g_free(cmp_pattern); g_free(cmp_str); return FALSE; } /* We found the current character in pattern, so move to the next. */ idx_pattern = g_utf8_next_char(idx_pattern); /* move idx_str to the next character as well, because the current * character has already been matched. */ idx_str = g_utf8_next_char(idx_str); }; g_free(cmp_pattern); g_free(cmp_str); return TRUE; } /************************************************************************** * URI/URL Functions **************************************************************************/ /* Originally lifted from * http://www.oreillynet.com/pub/a/network/excerpt/spcookbook_chap03/index3.html * ... and slightly modified to be a bit more rfc822 compliant * ... and modified a bit more to make domain checking rfc1035 compliant * with the exception permitted in rfc1101 for domains to start with digit * but not completely checking to avoid conflicts with IP addresses */ gboolean purple_email_is_valid(const char *address) { const char *c, *domain; static char *rfc822_specials = "()<>@,;:\\\"[]"; g_return_val_if_fail(address != NULL, FALSE); if (*address == '.') return FALSE; /* first we validate the name portion (name@domain) (rfc822)*/ for (c = address; *c; c++) { if (*c == '\"' && (c == address || *(c - 1) == '.' || *(c - 1) == '\"')) { while (*++c) { if (*c == '\\') { if (*c++ && *c < 127 && *c > 0 && *c != '\n' && *c != '\r') continue; else return FALSE; } if (*c == '\"') break; if (*c < ' ' || *c >= 127) return FALSE; } if (!*c++) return FALSE; if (*c == '@') break; if (*c != '.') return FALSE; continue; } if (*c == '@') break; if (*c <= ' ' || *c >= 127) return FALSE; if (strchr(rfc822_specials, *c)) return FALSE; } /* It's obviously not an email address if we didn't find an '@' above */ if (*c == '\0') return FALSE; /* strictly we should return false if (*(c - 1) == '.') too, but I think * we should permit user.@domain type addresses - they do work :) */ if (c == address) return FALSE; /* next we validate the domain portion (name@domain) (rfc1035 & rfc1011) */ if (!*(domain = ++c)) return FALSE; do { if (*c == '.' && (c == domain || *(c - 1) == '.' || *(c - 1) == '-')) return FALSE; if (*c == '-' && (*(c - 1) == '.' || *(c - 1) == '@')) return FALSE; if ((*c < '0' && *c != '-' && *c != '.') || (*c > '9' && *c < 'A') || (*c > 'Z' && *c < 'a') || (*c > 'z')) return FALSE; } while (*++c); if (*(c - 1) == '-') return FALSE; return ((c - domain) > 3 ? TRUE : FALSE); } /************************************************************************** * UTF8 String Functions **************************************************************************/ gchar * purple_utf8_try_convert(const char *str) { gsize converted; gchar *utf8; g_return_val_if_fail(str != NULL, NULL); if (g_utf8_validate(str, -1, NULL)) { return g_strdup(str); } utf8 = g_locale_to_utf8(str, -1, &converted, NULL, NULL); if (utf8 != NULL) return utf8; utf8 = g_convert(str, -1, "UTF-8", "ISO-8859-15", &converted, NULL, NULL); if ((utf8 != NULL) && (converted == strlen(str))) return utf8; g_free(utf8); return NULL; } int purple_utf8_strcasecmp(const char *a, const char *b) { char *a_norm = NULL; char *b_norm = NULL; int ret = -1; if(!a && b) return -1; else if(!b && a) return 1; else if(!a && !b) return 0; if(!g_utf8_validate(a, -1, NULL) || !g_utf8_validate(b, -1, NULL)) { purple_debug_error("purple_utf8_strcasecmp", "One or both parameters are invalid UTF8\n"); return ret; } a_norm = g_utf8_casefold(a, -1); b_norm = g_utf8_casefold(b, -1); ret = g_utf8_collate(a_norm, b_norm); g_free(a_norm); g_free(b_norm); return ret; } /* this is almost identical to purple_url_encode (hence purple_url_decode * being used above), but we want to keep certain characters unescaped * for compat reasons */ const char * purple_escape_filename(const char *str) { const char *iter; static char buf[BUF_LEN]; char utf_char[6]; guint i, j = 0; g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL); iter = str; for (; *iter && j < (BUF_LEN - 1) ; iter = g_utf8_next_char(iter)) { gunichar c = g_utf8_get_char(iter); /* If the character is an ASCII character and is alphanumeric, * or one of the specified values, no need to escape */ if (c < 128 && (g_ascii_isalnum(c) || c == '@' || c == '-' || c == '_' || c == '.' || c == '#')) { buf[j++] = c; } else { int bytes = g_unichar_to_utf8(c, utf_char); for (i = 0; (int)i < bytes; i++) { if (j > (BUF_LEN - 4)) break; if (i >= sizeof(utf_char)) { g_warn_if_reached(); break; } sprintf(buf + j, "%%%02x", utf_char[i] & 0xff); j += 3; } } } #ifdef _WIN32 /* File/Directory names in windows cannot end in periods/spaces. * http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx */ while (j > 0 && (buf[j - 1] == '.' || buf[j - 1] == ' ')) j--; #endif buf[j] = '\0'; return buf; }