Mercurial > ~mikael > mcabber > hg
diff mcabber/mcabber/utils.c @ 1668:41c26b7d2890
Install mcabber headers
* Change mcabber headers naming scheme
* Move 'src/' -> 'mcabber/'
* Add missing include <mcabber/config.h>'s
* Create and install clean config.h version in 'include/'
* Move "dirty" config.h version to 'mcabber/'
* Add $(top_srcdir) to compiler include path
* Update modules HOWTO
author | Myhailo Danylenko <isbear@ukrpost.net> |
---|---|
date | Mon, 18 Jan 2010 15:36:19 +0200 |
parents | mcabber/src/utils.c@c3c7d6d0348f |
children | f02e7076ccec |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/utils.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,789 @@ +/* + * utils.c -- Various utility functions + * + * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net> + * Some of the ut_* functions are derived from Cabber debug/log code. + * from_iso8601() comes from the Pidgin (libpurple) project. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + +#ifdef HAVE_LIBIDN +#include <idna.h> +#include <stringprep.h> +static char idnprep[1024]; +#endif + +#include <glib.h> +#include <glib/gprintf.h> + +/* For Cygwin (thanks go to Yitzchak Scott-Thoennes) */ +#ifdef __CYGWIN__ +# define timezonevar + extern long timezone; +#endif +#include <time.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <ctype.h> + +#include "utils.h" +#include "logprint.h" + +static int DebugEnabled; +static char *FName; + +// jidtodisp(jid) +// Strips the resource part from the jid +// The caller should g_free the result after use. +char *jidtodisp(const char *fjid) +{ + char *ptr; + char *alias; + + alias = g_strdup(fjid); + + if ((ptr = strchr(alias, JID_RESOURCE_SEPARATOR)) != NULL) { + *ptr = 0; + } + return alias; +} + +char *jid_get_username(const char *fjid) +{ + char *ptr; + char *username; + + username = g_strdup(fjid); + if ((ptr = strchr(username, JID_DOMAIN_SEPARATOR)) != NULL) { + *ptr = 0; + } + return username; +} + +char *compose_jid(const char *username, const char *servername, + const char *resource) +{ + char *fjid; + + if (!strchr(username, JID_DOMAIN_SEPARATOR)) { + fjid = g_strdup_printf("%s%c%s%c%s", username, + JID_DOMAIN_SEPARATOR, servername, + JID_RESOURCE_SEPARATOR, resource); + } else { + fjid = g_strdup_printf("%s%c%s", username, + JID_RESOURCE_SEPARATOR, resource); + } + return fjid; +} + +gboolean jid_equal(const char *jid1, const char *jid2) +{ + char *a,*b; + int ret; + if (!jid1 && !jid2) + return TRUE; + if (!jid1 || !jid2) + return FALSE; + + a = jidtodisp(jid1); + b = jidtodisp(jid2); + ret = strcasecmp(a, b); + g_free(a); + g_free(b); + return (ret == 0) ? TRUE : FALSE; +} + +// expand_filename(filename) +// Expand "~/" with the $HOME env. variable in a file name. +// The caller must free the string after use. +char *expand_filename(const char *fname) +{ + if (!fname) + return NULL; + if (!strncmp(fname, "~/", 2)) { + char *homedir = getenv("HOME"); + if (homedir) + return g_strdup_printf("%s%s", homedir, fname+1); + } + return g_strdup(fname); +} + +void fingerprint_to_hex(const unsigned char *fpr, char hex[49]) +{ + int i; + char *p; + + for (p = hex, i = 0; i < 15; i++, p+=3) + g_sprintf(p, "%02X:", fpr[i]); + g_sprintf(p, "%02X", fpr[i]); + hex[48] = '\0'; +} + +gboolean hex_to_fingerprint(const char *hex, char fpr[16]) +{ + int i; + char *p; + + if (strlen(hex) != 47) + return FALSE; + for (i = 0, p = (char*)hex; *p && *(p+1); i++, p += 3) + fpr[i] = (char) g_ascii_strtoull (p, NULL, 16); + return TRUE; +} + +void ut_InitDebug(int level, const char *filename) +{ + FILE *fp; + struct stat buf; + int err; + + if (level < 1) { + DebugEnabled = 0; + FName = NULL; + return; + } + + if (filename) + FName = expand_filename(filename); + else { + FName = getenv("HOME"); + if (!FName) + FName = g_strdup("/tmp/mcabberlog"); + else { + FName = g_strdup_printf("%s/mcabberlog", FName); + } + } + + DebugEnabled = level; + + fp = fopen(FName, "a"); + if (!fp) { + fprintf(stderr, "ERROR: Cannot open tracelog file\n"); + return; + } + + err = fstat(fileno(fp), &buf); + if (err || buf.st_uid != geteuid()) { + fclose(fp); + DebugEnabled = 0; + FName = NULL; + if (err) { + fprintf(stderr, "ERROR: cannot stat the tracelog file!\n"); + } else { + fprintf(stderr, "ERROR: tracelog file does not belong to you!\n"); + } + return; + } + fchmod(fileno(fp), S_IRUSR|S_IWUSR); + + fprintf(fp, "New trace log started.\n----------------------\n"); + fclose(fp); +} + +void ut_WriteLog(unsigned int flag, const char *data) +{ + if (!DebugEnabled || !FName) return; + + if (((DebugEnabled >= 2) && (flag & (LPRINT_LOG|LPRINT_DEBUG))) || + ((DebugEnabled == 1) && (flag & LPRINT_LOG))) { + FILE *fp = fopen(FName, "a+"); + if (!fp) { + scr_LogPrint(LPRINT_NORMAL, "ERROR: Cannot open tracelog file"); + return; + } + if (fputs(data, fp) == EOF) + scr_LogPrint(LPRINT_NORMAL, "ERROR: Cannot write to tracelog file"); + fclose(fp); + } +} + +// checkset_perm(name, setmode) +// Check the permissions of the "name" file/dir +// If setmode is true, correct the permissions if they are wrong +// Return values: -1 == bad file/dir, 0 == success, 1 == cannot correct +int checkset_perm(const char *name, unsigned int setmode) +{ + int fd; + struct stat buf; + +#ifdef __CYGWIN__ + // Permission checking isn't efficient on Cygwin + return 0; +#endif + + fd = stat(name, &buf); + if (fd == -1) return -1; + + if (buf.st_uid != geteuid()) { + scr_LogPrint(LPRINT_LOGNORM, "Wrong file owner [%s]", name); + return 1; + } + + if (buf.st_mode & (S_IRGRP | S_IWGRP | S_IXGRP) || + buf.st_mode & (S_IROTH | S_IWOTH | S_IXOTH)) { + if (setmode) { + mode_t newmode = 0; + scr_LogPrint(LPRINT_LOGNORM, "Bad permissions [%s]", name); + if (S_ISDIR(buf.st_mode)) + newmode |= S_IXUSR; + newmode |= S_IRUSR | S_IWUSR; + if (chmod(name, newmode)) { + scr_LogPrint(LPRINT_LOGNORM, "WARNING: Failed to correct permissions!"); + return 1; + } + scr_LogPrint(LPRINT_LOGNORM, "Permissions have been corrected"); + } else { + scr_LogPrint(LPRINT_LOGNORM, "WARNING: Bad permissions [%s]", name); + return 1; + } + } + + return 0; +} + +const char *ut_get_tmpdir(void) +{ + static const char *tmpdir; + const char *tmpvars[] = { "MCABBERTMPDIR", "TMP", "TMPDIR", "TEMP" }; + unsigned int i; + + if (tmpdir) + return tmpdir; + + for (i = 0; i < (sizeof(tmpvars) / sizeof(const char *)); i++) { + tmpdir = getenv(tmpvars[i]); + if (tmpdir && tmpdir[0] && tmpdir[0] == '/' && tmpdir[1]) { + // Looks ok. + return tmpdir; + } + } + + // Default temporary directory + tmpdir = "/tmp"; + return tmpdir; +} + +// to_iso8601(dststr, timestamp) +// Convert timestamp to iso8601 format, and store it in dststr. +// NOTE: dststr should be at last 19 chars long. +// Return should be 0 +int to_iso8601(char *dststr, time_t timestamp) +{ + struct tm *tm_time; + int ret; + + tm_time = gmtime(×tamp); + + ret = snprintf(dststr, 19, "%.4d%02d%02dT%02d:%02d:%02dZ", + (int)(1900+tm_time->tm_year), tm_time->tm_mon+1, tm_time->tm_mday, + tm_time->tm_hour, tm_time->tm_min, tm_time->tm_sec); + + return ((ret == -1) ? -1 : 0); +} + +// from_iso8601(timestamp, utc) +// This function came from the Pidgin project, gaim_str_to_time(). +// (Actually date may not be pure iso-8601) +// Thanks, guys! +// ** Modified by somian 10 Apr 2006 with advice from ysth. +time_t from_iso8601(const char *timestamp, int utc) +{ + struct tm t; + time_t retval = 0; + char buf[32]; + char *c; + int tzoff = 0; + int hms_succ = 0; + int tmpyear; + + time(&retval); + localtime_r(&retval, &t); + + /* Reset time to midnight (00:00:00) */ + t.tm_hour = t.tm_min = t.tm_sec = 0; + + snprintf(buf, sizeof(buf), "%s", timestamp); + c = buf; + + /* 4 digit year */ + if (!sscanf(c, "%04d", &tmpyear)) return 0; + t.tm_year = tmpyear; + c+=4; + if (*c == '-') + c++; + + t.tm_year -= 1900; + + /* 2 digit month */ + if (!sscanf(c, "%02d", &t.tm_mon)) return 0; + c+=2; + if (*c == '-') + c++; + + t.tm_mon -= 1; + + /* 2 digit day */ + if (!sscanf(c, "%02d", &t.tm_mday)) return 0; + c+=2; + if (*c == 'T' || *c == '.') { /* we have more than a date, keep going */ + c++; /* skip the "T" */ + + /* 2 digit hour */ + if (sscanf(c, "%02d:%02d:%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3) + { + hms_succ = 1; + c += 8; + } + else if (sscanf(c, "%02d%02d%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3) + { + hms_succ = 1; + c += 6; + } + + if (hms_succ) { + int tzhrs, tzmins; + + if (*c == '.') /* dealing with precision we don't care about */ + c += 4; + + if ((*c == '+' || *c == '-') && + sscanf(c+1, "%02d:%02d", &tzhrs, &tzmins)) { + tzoff = tzhrs*60*60 + tzmins*60; + if (*c == '+') + tzoff *= -1; + } + + if (tzoff || utc) { +#ifdef HAVE_TM_GMTOFF + tzoff += t.tm_gmtoff; +#else +# ifdef HAVE_TIMEZONE + tzset(); /* making sure */ + tzoff -= timezone; +# endif +#endif + } + } + } + + t.tm_isdst = -1; + + retval = mktime(&t); + + retval += tzoff; + + return retval; +} + +/** + * Derived from libjabber/jid.c, because the libjabber version is not + * really convenient for our usage. + * + * Check if the full JID is valid + * Return 0 if it is valid, non zero otherwise + */ +int check_jid_syntax(const char *fjid) +{ + const char *str; + const char *domain, *resource; + int domlen; +#ifdef HAVE_LIBIDN + char *idnpp; + int r; +#endif + + if (!fjid) return 1; + + domain = strchr(fjid, JID_DOMAIN_SEPARATOR); + + /* the username is optional */ + if (!domain) { + domain = fjid; + } else { + /* node identifiers may not be longer than 1023 bytes */ + if ((domain == fjid) || (domain-fjid > 1023)) + return 1; + domain++; + +#ifdef HAVE_LIBIDN + idnpp = idnprep; + str = fjid; + while (*str != JID_DOMAIN_SEPARATOR) + *idnpp++ = *str++; + *idnpp = 0; + + r = stringprep(idnprep, 1023, 0, stringprep_xmpp_nodeprep); + if (r != STRINGPREP_OK || !idnprep[0]) + return 1; + /* the username looks okay */ +#else + /* check for low and invalid ascii characters in the username */ + for (str = fjid; *str != JID_DOMAIN_SEPARATOR; str++) { + if (*str <= ' ' || *str == ':' || *str == JID_DOMAIN_SEPARATOR || + *str == '<' || *str == '>' || *str == '\'' || + *str == '"' || *str == '&') { + return 1; + } + } + /* the username is okay as far as we can tell without LIBIDN */ +#endif + } + + resource = strchr(domain, JID_RESOURCE_SEPARATOR); + + /* the resource is optional */ + if (resource) { + domlen = resource - domain; + resource++; + /* resources may not be longer than 1023 bytes */ + if ((*resource == '\0') || strlen(resource) > 1023) + return 1; +#ifdef HAVE_LIBIDN + strncpy(idnprep, resource, sizeof(idnprep)); + r = stringprep(idnprep, 1023, 0, stringprep_xmpp_resourceprep); + if (r != STRINGPREP_OK || !idnprep[0]) + return 1; +#endif + } else { + domlen = strlen(domain); + } + + /* there must be a domain identifier */ + if (domlen == 0) return 1; + + /* and it must not be longer than 1023 bytes */ + if (domlen > 1023) return 1; + +#ifdef HAVE_LIBIDN + idnpp = idnprep; + str = domain; + while (*str != '\0' && *str != JID_RESOURCE_SEPARATOR) + *idnpp++ = *str++; + *idnpp = 0; + + r = stringprep_nameprep(idnprep, 1023); + if (r != STRINGPREP_OK || !idnprep[0]) + return 1; + + if (idna_to_ascii_8z(idnprep, &idnpp, IDNA_USE_STD3_ASCII_RULES) != + IDNA_SUCCESS) + return 1; + else + free(idnpp); +#else + /* make sure the hostname is valid characters */ + for (str = domain; *str != '\0' && *str != JID_RESOURCE_SEPARATOR; str++) { + if (!(isalnum(*str) || *str == '.' || *str == '-' || *str == '_')) + return 1; + } +#endif + + /* it's okay as far as we can tell */ + return 0; +} + + +inline void mc_strtolower(char *str) +{ + if (!str) return; + for ( ; *str; str++) + *str = tolower(*str); +} + +// strip_arg_special_chars(string) +// Remove quotes and backslashes before an escaped quote +// Only quotes need a backslash +// Ex.: ["a b"] -> [a b]; [a\"b] -> [a"b] +void strip_arg_special_chars(char *s) +{ + int instring = FALSE; + int escape = FALSE; + char *p; + + if (!s) return; + + for (p = s; *p; p++) { + if (*p == '"') { + if (!escape) { + instring = !instring; + strcpy(p, p+1); + p--; + } else + escape = FALSE; + } else if (*p == '\\') { + if (!escape) { + strcpy(p, p+1); + p--; + } + escape = !escape; + } else + escape = FALSE; + } +} + +// split_arg(arg, n, preservelast) +// Split the string arg into a maximum of n pieces, taking care of +// double quotes. +// Return a null-terminated array of strings. This array should be freed +// by the caller after use, for example with free_arg_lst(). +// If dontstriplast is true, the Nth argument isn't stripped (i.e. no +// processing of quote chars) +char **split_arg(const char *arg, unsigned int n, int dontstriplast) +{ + char **arglst; + const char *p, *start, *end; + unsigned int i = 0; + int instring = FALSE; + int escape = FALSE; + + arglst = g_new0(char*, n+1); + + if (!arg || !n) return arglst; + + // Skip leading space + for (start = arg; *start && *start == ' '; start++) ; + // End of string pointer + for (end = start; *end; end++) ; + // Skip trailing space + while (end > start+1 && *(end-1) == ' ') + end--; + + for (p = start; p < end; p++) { + if (*p == '"' && !escape) + instring = !instring; + if (*p == '\\' && !escape) + escape = TRUE; + else if (escape) + escape = FALSE; + if (*p == ' ' && !instring && i+1 < n) { + // end of parameter + *(arglst+i) = g_strndup(start, p-start); + strip_arg_special_chars(*(arglst+i)); + for (start = p+1; *start && *start == ' '; start++) ; + p = start-1; + i++; + } + } + + if (start < end) { + *(arglst+i) = g_strndup(start, end-start); + if (!dontstriplast || i+1 < n) + strip_arg_special_chars(*(arglst+i)); + } + + return arglst; +} + +// free_arg_lst(arglst) +// Free an array allocated by split_arg() +void free_arg_lst(char **arglst) +{ + char **arg_elt; + + for (arg_elt = arglst; *arg_elt; arg_elt++) + g_free(*arg_elt); + g_free(arglst); +} + +// replace_nl_with_dots(bufstr) +// Replace '\n' with "(...)" (or with a NUL if the string is too short) +void replace_nl_with_dots(char *bufstr) +{ + char *p = strchr(bufstr, '\n'); + if (p) { + if (strlen(p) >= 5) + strcpy(p, "(...)"); + else + *p = 0; + } +} + +// ut_expand_tabs(text) +// Expand tabs and filter out some bad chars in string text. +// If there is no tab and no bad chars in the string, a pointer to text +// is returned (be careful _not_ to free the pointer in this case). +// If there are some tabs or bad chars, a new string with expanded chars +// and no bad chars is returned; this is up to the caller to free this +// string after use. +char *ut_expand_tabs(const char *text) +{ + char *xtext, *linestart; + char *p, *q; + guint n = 0, bc = 0; + + xtext = (char*)text; + for (p=xtext; *p; p++) + if (*p == '\t') + n++; + else if (*p == '\x0d') + bc++; + // XXX Are there other special chars we should filter out? + + if (!n && !bc) + return (char*)text; + + xtext = g_new(char, strlen(text) + 1 + 8*n); + p = (char*)text; + q = linestart = xtext; + do { + if (*p == '\t') { + do { *q++ = ' '; } while ((q-linestart)%8); + } else if (*p != '\x0d') { + *q++ = *p; + if (*p =='\n') + linestart = q; + } + } while (*p++); + + return xtext; +} + + +/* Cygwin's newlib does not have strcasestr() */ +/* The author of the code before the endif is + * Jeffrey Stedfast <fejj@ximian.com> + * and this code is reusable in compliance with the GPL v2. -- somian */ + +#if !defined(HAVE_STRCASESTR) + +# define lowercase(c) (isupper ((int) (c)) ? tolower ((int) (c)) : (int) (c)) +# define bm_index(c, icase) ((icase) ? lowercase (c) : (int) (c)) +# define bm_equal(c1, c2, icase) ((icase) ? lowercase (c1) == lowercase (c2) : (c1) == (c2)) + +/* FIXME: this is just a guess... should really do some performace tests to get an accurate measure */ +# define bm_optimal(hlen, nlen) (((hlen) ? (hlen) > 20 : 1) && (nlen) > 10 ? 1 : 0) + +static unsigned char * +__boyer_moore (const unsigned char *haystack, size_t haystacklen, + const unsigned char *needle, size_t needlelen, int icase) +{ + register unsigned char *hc_ptr, *nc_ptr; + unsigned char *he_ptr, *ne_ptr, *h_ptr; + size_t skiptable[256], n; + register int i; + +#ifdef BOYER_MOORE_CHECKS + /* we don't need to do these checks since memmem/strstr/etc do it already */ + /* if the haystack is shorter than the needle then we can't possibly match */ + if (haystacklen < needlelen) + return NULL; + + /* instant match if the pattern buffer is 0-length */ + if (needlelen == 0) + return (unsigned char *) haystack; +#endif /* BOYER_MOORE_CHECKS */ + + /* set a pointer at the end of each string */ + ne_ptr = (unsigned char *) needle + needlelen - 1; + he_ptr = (unsigned char *) haystack + haystacklen - 1; + + /* create our skip table */ + for (i = 0; i < 256; i++) + skiptable[i] = needlelen; + for (nc_ptr = (unsigned char *) needle; nc_ptr < ne_ptr; nc_ptr++) + skiptable[bm_index (*nc_ptr, icase)] = (size_t) (ne_ptr - nc_ptr); + + h_ptr = (unsigned char *) haystack; + while (haystacklen >= needlelen) { + hc_ptr = h_ptr + needlelen - 1; /* set the haystack compare pointer */ + nc_ptr = ne_ptr; /* set the needle compare pointer */ + + /* work our way backwards till they don't match */ + for (i = 0; nc_ptr > (unsigned char *) needle; nc_ptr--, hc_ptr--, i++) + if (!bm_equal (*nc_ptr, *hc_ptr, icase)) + break; + + if (!bm_equal (*nc_ptr, *hc_ptr, icase)) { + n = skiptable[bm_index (*hc_ptr, icase)]; + if (n == needlelen && i) + if (bm_equal (*ne_ptr, ((unsigned char *) needle)[0], icase)) + n--; + h_ptr += n; + haystacklen -= n; + } else + return (unsigned char *) h_ptr; + } + + return NULL; +} + +/* + * strcasestr: + * @haystack: string to search + * @needle: substring to search for + * + * Finds the first occurence of the substring @needle within the + * string @haystack ignoring case. + * + * Returns a pointer to the beginning of the substring match within + * @haystack, or NULL if the substring is not found. + **/ +char * +strcasestr (const char *haystack, const char *needle) +{ + register unsigned char *h, *n, *hc, *nc; + size_t needlelen; + + needlelen = strlen (needle); + + if (needlelen == 0) { + return (char *) haystack; + } else if (bm_optimal (0, needlelen)) { + return (char *) __boyer_moore ((const unsigned char *) haystack, + strlen (haystack), + (const unsigned char *) needle, + needlelen, 1); + } + + h = (unsigned char *) haystack; + n = (unsigned char *) needle; + + while (*(h + needlelen - 1)) { + if (lowercase (*h) == lowercase (*n)) { + for (hc = h + 1, nc = n + 1; *hc && *nc; hc++, nc++) + if (lowercase (*hc) != lowercase (*nc)) + break; + + if (!*nc) + return (char *) h; + } + h++; + } + return NULL; +} +#endif /* !HAVE_STRCASESTR */ + +// startswith(str, word, ignore_case) +// Returns TRUE if string str starts with word. +int startswith(const char *str, const char *word, guint ignore_case) +{ + if (ignore_case && !strncasecmp(str, word, strlen(word))) + return TRUE; + else if (!ignore_case && !strncmp(str, word, strlen(word))) + return TRUE; + return FALSE; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */