1   /********************************************************************\
2   * BitlBee -- An IRC to other IM-networks gateway                     *
3   *                                                                    *
4   * Copyright 2002-2012 Wilmer van der Gaast and others                *
5   \********************************************************************/
6 
7 /*
8  * Various utility functions. Some are copied from Gaim to support the
9  * IM-modules, most are from BitlBee.
10  *
11  * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
12  *                          (and possibly other members of the Gaim team)
13  * Copyright 2002-2012 Wilmer van der Gaast <wilmer@gaast.net>
14  */
15 
16 /*
17   This program is free software; you can redistribute it and/or modify
18   it under the terms of the GNU General Public License as published by
19   the Free Software Foundation; either version 2 of the License, or
20   (at your option) any later version.
21 
22   This program is distributed in the hope that it will be useful,
23   but WITHOUT ANY WARRANTY; without even the implied warranty of
24   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25   GNU General Public License for more details.
26 
27   You should have received a copy of the GNU General Public License with
28   the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
29   if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
30   Fifth Floor, Boston, MA  02110-1301  USA
31 */
32 
33 #define BITLBEE_CORE
34 #include "nogaim.h"
35 #include "base64.h"
36 #include "md5.h"
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <ctype.h>
41 #include <glib.h>
42 #include <time.h>
43 
44 #ifdef HAVE_RESOLV_A
45 #include <arpa/nameser.h>
46 #include <resolv.h>
47 #endif
48 
49 #include "md5.h"
50 #include "ssl_client.h"
51 
strip_linefeed(gchar * text)52 void strip_linefeed(gchar *text)
53 {
54 	int i, j;
55 	gchar *text2 = g_malloc(strlen(text) + 1);
56 
57 	for (i = 0, j = 0; text[i]; i++) {
58 		if (text[i] != '\r') {
59 			text2[j++] = text[i];
60 		}
61 	}
62 	text2[j] = '\0';
63 
64 	strcpy(text, text2);
65 	g_free(text2);
66 }
67 
get_time(int year,int month,int day,int hour,int min,int sec)68 time_t get_time(int year, int month, int day, int hour, int min, int sec)
69 {
70 	struct tm tm;
71 
72 	memset(&tm, 0, sizeof(struct tm));
73 	tm.tm_year = year - 1900;
74 	tm.tm_mon = month - 1;
75 	tm.tm_mday = day;
76 	tm.tm_hour = hour;
77 	tm.tm_min = min;
78 	tm.tm_sec = sec >= 0 ? sec : time(NULL) % 60;
79 
80 	return mktime(&tm);
81 }
82 
mktime_utc(struct tm * tp)83 time_t mktime_utc(struct tm *tp)
84 {
85 	struct tm utc;
86 	time_t res, tres;
87 
88 	tp->tm_isdst = -1;
89 	res = mktime(tp);
90 	/* Problem is, mktime() just gave us the GMT timestamp for the
91 	   given local time... While the given time WAS NOT local. So
92 	   we should fix this now.
93 
94 	   Now I could choose between messing with environment variables
95 	   (kludgy) or using timegm() (not portable)... Or doing the
96 	   following, which I actually prefer...
97 
98 	   tzset() may also work but in other places I actually want to
99 	   use local time.
100 
101 	   FFFFFFFFFFFFFFFFFFFFFUUUUUUUUUUUUUUUUUUUU!! */
102 	gmtime_r(&res, &utc);
103 	utc.tm_isdst = -1;
104 	if (utc.tm_hour == tp->tm_hour && utc.tm_min == tp->tm_min) {
105 		/* Sweet! We're in UTC right now... */
106 		return res;
107 	}
108 
109 	tres = mktime(&utc);
110 	res += res - tres;
111 
112 	/* Yes, this is a hack. And it will go wrong around DST changes.
113 	   BUT this is more likely to be threadsafe than messing with
114 	   environment variables, and possibly more portable... */
115 
116 	return res;
117 }
118 
119 typedef struct htmlentity {
120 	char code[7];
121 	char is[3];
122 } htmlentity_t;
123 
124 static const htmlentity_t ent[] =
125 {
126 	{ "lt",     "<" },
127 	{ "gt",     ">" },
128 	{ "amp",    "&" },
129 	{ "apos",   "'" },
130 	{ "quot",   "\"" },
131 	{ "aacute", "á" },
132 	{ "eacute", "é" },
133 	{ "iacute", "é" },
134 	{ "oacute", "ó" },
135 	{ "uacute", "ú" },
136 	{ "agrave", "à" },
137 	{ "egrave", "è" },
138 	{ "igrave", "ì" },
139 	{ "ograve", "ò" },
140 	{ "ugrave", "ù" },
141 	{ "acirc",  "â" },
142 	{ "ecirc",  "ê" },
143 	{ "icirc",  "î" },
144 	{ "ocirc",  "ô" },
145 	{ "ucirc",  "û" },
146 	{ "auml",   "ä" },
147 	{ "euml",   "ë" },
148 	{ "iuml",   "ï" },
149 	{ "ouml",   "ö" },
150 	{ "uuml",   "ü" },
151 	{ "nbsp",   " " },
152 	{ "",        ""  }
153 };
154 
strip_html(char * in)155 void strip_html(char *in)
156 {
157 	char *start = in;
158 	char out[strlen(in) + 1];
159 	char *s = out, *cs;
160 	int i, matched;
161 	int taglen;
162 
163 	memset(out, 0, sizeof(out));
164 
165 	while (*in) {
166 		if (*in == '<' && (g_ascii_isalpha(*(in + 1)) || *(in + 1) == '/')) {
167 			/* If in points at a < and in+1 points at a letter or a slash, this is probably
168 			   a HTML-tag. Try to find a closing > and continue there. If the > can't be
169 			   found, assume that it wasn't a HTML-tag after all. */
170 
171 			cs = in;
172 
173 			while (*in && *in != '>') {
174 				in++;
175 			}
176 
177 			taglen = in - cs - 1;   /* not <0 because the above loop runs at least once */
178 			if (*in) {
179 				if (g_strncasecmp(cs + 1, "b", taglen) == 0) {
180 					*(s++) = '\x02';
181 				} else if (g_strncasecmp(cs + 1, "/b", taglen) == 0) {
182 					*(s++) = '\x02';
183 				} else if (g_strncasecmp(cs + 1, "i", taglen) == 0) {
184 					*(s++) = '\x1f';
185 				} else if (g_strncasecmp(cs + 1, "/i", taglen) == 0) {
186 					*(s++) = '\x1f';
187 				} else if (g_strncasecmp(cs + 1, "br", taglen) == 0) {
188 					*(s++) = '\n';
189 				} else if (g_strncasecmp(cs + 1, "br/", taglen) == 0) {
190 					*(s++) = '\n';
191 				} else if (g_strncasecmp(cs + 1, "br /", taglen) == 0) {
192 					*(s++) = '\n';
193 				}
194 				in++;
195 			} else {
196 				in = cs;
197 				*(s++) = *(in++);
198 			}
199 		} else if (*in == '&') {
200 			cs = ++in;
201 			while (*in && g_ascii_isalpha(*in)) {
202 				in++;
203 			}
204 
205 			if (*in == ';') {
206 				in++;
207 			}
208 			matched = 0;
209 
210 			for (i = 0; *ent[i].code; i++) {
211 				if (g_strncasecmp(ent[i].code, cs, strlen(ent[i].code)) == 0) {
212 					int j;
213 
214 					for (j = 0; ent[i].is[j]; j++) {
215 						*(s++) = ent[i].is[j];
216 					}
217 
218 					matched = 1;
219 					break;
220 				}
221 			}
222 
223 			/* None of the entities were matched, so return the string */
224 			if (!matched) {
225 				in = cs - 1;
226 				*(s++) = *(in++);
227 			}
228 		} else {
229 			*(s++) = *(in++);
230 		}
231 	}
232 
233 	strcpy(start, out);
234 }
235 
escape_html(const char * html)236 char *escape_html(const char *html)
237 {
238 	const char *c = html;
239 	GString *ret;
240 	char *str;
241 
242 	if (html == NULL) {
243 		return(NULL);
244 	}
245 
246 	ret = g_string_new("");
247 
248 	while (*c) {
249 		switch (*c) {
250 		case '&':
251 			ret = g_string_append(ret, "&amp;");
252 			break;
253 		case '<':
254 			ret = g_string_append(ret, "&lt;");
255 			break;
256 		case '>':
257 			ret = g_string_append(ret, "&gt;");
258 			break;
259 		case '"':
260 			ret = g_string_append(ret, "&quot;");
261 			break;
262 		default:
263 			ret = g_string_append_c(ret, *c);
264 		}
265 		c++;
266 	}
267 
268 	str = ret->str;
269 	g_string_free(ret, FALSE);
270 	return(str);
271 }
272 
273 /* Decode%20a%20file%20name						*/
http_decode(char * s)274 void http_decode(char *s)
275 {
276 	char *t;
277 	int i, j, k;
278 
279 	t = g_new(char, strlen(s) + 1);
280 
281 	for (i = j = 0; s[i]; i++, j++) {
282 		if (s[i] == '%') {
283 			if (sscanf(s + i + 1, "%2x", &k)) {
284 				t[j] = k;
285 				i += 2;
286 			} else {
287 				*t = 0;
288 				break;
289 			}
290 		} else {
291 			t[j] = s[i];
292 		}
293 	}
294 	t[j] = 0;
295 
296 	strcpy(s, t);
297 	g_free(t);
298 }
299 
300 /* Warning: This one explodes the string. Worst-cases can make the string 3x its original size! */
301 /* This function is safe, but make sure you call it safely as well! */
http_encode(char * s)302 void http_encode(char *s)
303 {
304 	char t[strlen(s) + 1];
305 	int i, j;
306 
307 	strcpy(t, s);
308 	for (i = j = 0; t[i]; i++, j++) {
309 		/* Warning: g_ascii_isalnum() is locale-aware, so don't use it here! */
310 		if ((t[i] >= 'A' && t[i] <= 'Z') ||
311 		    (t[i] >= 'a' && t[i] <= 'z') ||
312 		    (t[i] >= '0' && t[i] <= '9') ||
313 		    strchr("._-~", t[i])) {
314 			s[j] = t[i];
315 		} else {
316 			sprintf(s + j, "%%%02X", ((unsigned char *) t)[i]);
317 			j += 2;
318 		}
319 	}
320 	s[j] = 0;
321 }
322 
323 /* Strip newlines from a string. Modifies the string passed to it. */
strip_newlines(char * source)324 char *strip_newlines(char *source)
325 {
326 	int i;
327 
328 	for (i = 0; source[i] != '\0'; i++) {
329 		if (source[i] == '\n' || source[i] == '\r') {
330 			source[i] = ' ';
331 		}
332 	}
333 
334 	return source;
335 }
336 
337 /* Convert from one charset to another.
338 
339    from_cs, to_cs: Source and destination charsets
340    src, dst: Source and destination strings
341    size: Size if src. 0 == use strlen(). strlen() is not reliable for UNICODE/UTF16 strings though.
342    maxbuf: Maximum number of bytes to write to dst
343 
344    Returns the number of bytes written to maxbuf or -1 on an error.
345 */
do_iconv(char * from_cs,char * to_cs,char * src,char * dst,size_t size,size_t maxbuf)346 signed int do_iconv(char *from_cs, char *to_cs, char *src, char *dst, size_t size, size_t maxbuf)
347 {
348 	GIConv cd;
349 	size_t res;
350 	size_t inbytesleft, outbytesleft;
351 	char *inbuf = src;
352 	char *outbuf = dst;
353 
354 	cd = g_iconv_open(to_cs, from_cs);
355 	if (cd == (GIConv) - 1) {
356 		return -1;
357 	}
358 
359 	inbytesleft = size ? size : strlen(src);
360 	outbytesleft = maxbuf - 1;
361 	res = g_iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
362 	*outbuf = '\0';
363 	g_iconv_close(cd);
364 
365 	if (res != 0) {
366 		return -1;
367 	} else {
368 		return outbuf - dst;
369 	}
370 }
371 
372 /* A wrapper for /dev/urandom.
373  * If /dev/urandom is not present or not usable, it calls abort()
374  * to prevent bitlbee from working without a decent entropy source */
random_bytes(unsigned char * buf,int count)375 void random_bytes(unsigned char *buf, int count)
376 {
377 	int fd;
378 
379 	if (((fd = open("/dev/urandom", O_RDONLY)) == -1) ||
380 	    (read(fd, buf, count) == -1)) {
381 		log_message(LOGLVL_ERROR, "/dev/urandom not present - aborting");
382 		abort();
383 	}
384 
385 	close(fd);
386 }
387 
is_bool(char * value)388 int is_bool(char *value)
389 {
390 	if (*value == 0) {
391 		return 0;
392 	}
393 
394 	if ((g_strcasecmp(value,
395 	                  "true") == 0) || (g_strcasecmp(value, "yes") == 0) || (g_strcasecmp(value, "on") == 0)) {
396 		return 1;
397 	}
398 	if ((g_strcasecmp(value,
399 	                  "false") == 0) || (g_strcasecmp(value, "no") == 0) || (g_strcasecmp(value, "off") == 0)) {
400 		return 1;
401 	}
402 
403 	while (*value) {
404 		if (!g_ascii_isdigit(*value)) {
405 			return 0;
406 		} else {
407 			value++;
408 		}
409 	}
410 
411 	return 1;
412 }
413 
bool2int(char * value)414 int bool2int(char *value)
415 {
416 	int i;
417 
418 	if ((g_strcasecmp(value,
419 	                  "true") == 0) || (g_strcasecmp(value, "yes") == 0) || (g_strcasecmp(value, "on") == 0)) {
420 		return 1;
421 	}
422 	if ((g_strcasecmp(value,
423 	                  "false") == 0) || (g_strcasecmp(value, "no") == 0) || (g_strcasecmp(value, "off") == 0)) {
424 		return 0;
425 	}
426 
427 	if (sscanf(value, "%d", &i) == 1) {
428 		return i;
429 	}
430 
431 	return 0;
432 }
433 
srv_lookup(char * service,char * protocol,char * domain)434 struct ns_srv_reply **srv_lookup(char *service, char *protocol, char *domain)
435 {
436 	struct ns_srv_reply **replies = NULL;
437 
438 #ifdef HAVE_RESOLV_A
439 	struct ns_srv_reply *reply = NULL;
440 	char name[1024];
441 	unsigned char querybuf[1024];
442 	const unsigned char *buf;
443 	ns_msg nsh;
444 	ns_rr rr;
445 	int n, len, size;
446 
447 	g_snprintf(name, sizeof(name), "_%s._%s.%s", service, protocol, domain);
448 
449 	if ((size = res_query(name, ns_c_in, ns_t_srv, querybuf, sizeof(querybuf))) <= 0) {
450 		return NULL;
451 	}
452 
453 	if (ns_initparse(querybuf, size, &nsh) != 0) {
454 		return NULL;
455 	}
456 
457 	n = 0;
458 	while (ns_parserr(&nsh, ns_s_an, n, &rr) == 0) {
459 		char name[NS_MAXDNAME];
460 
461 		if (ns_rr_rdlen(rr) < 7) {
462 			break;
463 		}
464 
465 		buf = ns_rr_rdata(rr);
466 
467 		if (dn_expand(querybuf, querybuf + size, &buf[6], name, NS_MAXDNAME) == -1) {
468 			break;
469 		}
470 
471 		len = strlen(name) + 1;
472 
473 		reply = g_malloc(sizeof(struct ns_srv_reply) + len);
474 		memcpy(reply->name, name, len);
475 
476 		reply->prio = (buf[0] << 8) | buf[1];
477 		reply->weight = (buf[2] << 8) | buf[3];
478 		reply->port = (buf[4] << 8) | buf[5];
479 
480 		n++;
481 		replies = g_renew(struct ns_srv_reply *, replies, n + 1);
482 		replies[n - 1] = reply;
483 	}
484 	if (replies) {
485 		replies[n] = NULL;
486 	}
487 #endif
488 
489 	return replies;
490 }
491 
srv_free(struct ns_srv_reply ** srv)492 void srv_free(struct ns_srv_reply **srv)
493 {
494 	int i;
495 
496 	if (srv == NULL) {
497 		return;
498 	}
499 
500 	for (i = 0; srv[i]; i++) {
501 		g_free(srv[i]);
502 	}
503 	g_free(srv);
504 }
505 
word_wrap(const char * msg,int line_len)506 char *word_wrap(const char *msg, int line_len)
507 {
508 	GString *ret = g_string_sized_new(strlen(msg) + 16);
509 
510 	while (strlen(msg) > line_len) {
511 		int i;
512 
513 		/* First try to find out if there's a newline already. Don't
514 		   want to add more splits than necessary. */
515 		for (i = line_len; i > 0 && msg[i] != '\n'; i--) {
516 			;
517 		}
518 		if (msg[i] == '\n') {
519 			g_string_append_len(ret, msg, i + 1);
520 			msg += i + 1;
521 			continue;
522 		}
523 
524 		for (i = line_len; i > 0; i--) {
525 			if (msg[i] == '-') {
526 				g_string_append_len(ret, msg, i + 1);
527 				g_string_append_c(ret, '\n');
528 				msg += i + 1;
529 				break;
530 			} else if (msg[i] == ' ') {
531 				g_string_append_len(ret, msg, i);
532 				g_string_append_c(ret, '\n');
533 				msg += i + 1;
534 				break;
535 			}
536 		}
537 		if (i == 0) {
538 			const char *end;
539 			size_t len;
540 
541 			g_utf8_validate(msg, line_len, &end);
542 
543 			len = (end != msg) ? end - msg : line_len;
544 
545 			g_string_append_len(ret, msg, len);
546 			g_string_append_c(ret, '\n');
547 			msg += len;
548 		}
549 	}
550 	g_string_append(ret, msg);
551 
552 	return g_string_free(ret, FALSE);
553 }
554 
ssl_sockerr_again(void * ssl)555 gboolean ssl_sockerr_again(void *ssl)
556 {
557 	if (ssl) {
558 		return ssl_errno == SSL_AGAIN;
559 	} else {
560 		return sockerr_again();
561 	}
562 }
563 
564 /* Returns values: -1 == Failure (base64-decoded to something unexpected)
565                     0 == Okay
566                     1 == Password doesn't match the hash. */
md5_verify_password(char * password,char * hash)567 int md5_verify_password(char *password, char *hash)
568 {
569 	md5_byte_t *pass_dec = NULL;
570 	md5_byte_t pass_md5[16];
571 	md5_state_t md5_state;
572 	int ret = -1, i;
573 
574 	if (base64_decode(hash, &pass_dec) == 21) {
575 		md5_init(&md5_state);
576 		md5_append(&md5_state, (md5_byte_t *) password, strlen(password));
577 		md5_append(&md5_state, (md5_byte_t *) pass_dec + 16, 5);  /* Hmmm, salt! */
578 		md5_finish(&md5_state, pass_md5);
579 
580 		for (i = 0; i < 16; i++) {
581 			if (pass_dec[i] != pass_md5[i]) {
582 				ret = 1;
583 				break;
584 			}
585 		}
586 
587 		/* If we reached the end of the loop, it was a match! */
588 		if (i == 16) {
589 			ret = 0;
590 		}
591 	}
592 
593 	g_free(pass_dec);
594 
595 	return ret;
596 }
597 
598 /* Split commands (root-style, *not* IRC-style). Handles "quoting of"
599    white\ space in 'various ways'. Returns a NULL-terminated static
600    char** so watch out with nested use! Definitely not thread-safe. */
split_command_parts(char * command,int limit)601 char **split_command_parts(char *command, int limit)
602 {
603 	static char *cmd[IRC_MAX_ARGS + 1];
604 	char *s, q = 0;
605 	int k;
606 
607 	memset(cmd, 0, sizeof(cmd));
608 	cmd[0] = command;
609 	k = 1;
610 	for (s = command; *s && k < IRC_MAX_ARGS; s++) {
611 		if (*s == ' ' && !q) {
612 			*s = 0;
613 			while (*++s == ' ') {
614 				;
615 			}
616 			if (k != limit && (*s == '"' || *s == '\'')) {
617 				q = *s;
618 				s++;
619 			}
620 			if (*s) {
621 				cmd[k++] = s;
622 				if (limit && k > limit) {
623 					break;
624 				}
625 				s--;
626 			} else {
627 				break;
628 			}
629 		} else if (*s == '\\' && ((!q && s[1]) || (q && q == s[1]))) {
630 			char *cpy;
631 
632 			for (cpy = s; *cpy; cpy++) {
633 				cpy[0] = cpy[1];
634 			}
635 		} else if (*s == q) {
636 			q = *s = 0;
637 		}
638 	}
639 
640 	/* Full zero-padding for easier argc checking. */
641 	while (k <= IRC_MAX_ARGS) {
642 		cmd[k++] = NULL;
643 	}
644 
645 	return cmd;
646 }
647 
get_rfc822_header(const char * text,const char * header,int len)648 char *get_rfc822_header(const char *text, const char *header, int len)
649 {
650 	int hlen = strlen(header), i;
651 	const char *ret;
652 
653 	if (text == NULL) {
654 		return NULL;
655 	}
656 
657 	if (len == 0) {
658 		len = strlen(text);
659 	}
660 
661 	i = 0;
662 	while ((i + hlen) < len) {
663 		/* Maybe this is a bit over-commented, but I just hate this part... */
664 		if (g_strncasecmp(text + i, header, hlen) == 0) {
665 			/* Skip to the (probable) end of the header */
666 			i += hlen;
667 
668 			/* Find the first non-[: \t] character */
669 			while (i < len && (text[i] == ':' || text[i] == ' ' || text[i] == '\t')) {
670 				i++;
671 			}
672 
673 			/* Make sure we're still inside the string */
674 			if (i >= len) {
675 				return(NULL);
676 			}
677 
678 			/* Save the position */
679 			ret = text + i;
680 
681 			/* Search for the end of this line */
682 			while (i < len && text[i] != '\r' && text[i] != '\n') {
683 				i++;
684 			}
685 
686 			/* Copy the found data */
687 			return(g_strndup(ret, text + i - ret));
688 		}
689 
690 		/* This wasn't the header we were looking for, skip to the next line. */
691 		while (i < len && (text[i] != '\r' && text[i] != '\n')) {
692 			i++;
693 		}
694 		while (i < len && (text[i] == '\r' || text[i] == '\n')) {
695 			i++;
696 		}
697 
698 		/* End of headers? */
699 		if ((i >= 4 && strncmp(text + i - 4, "\r\n\r\n", 4) == 0) ||
700 		    (i >= 2 && (strncmp(text + i - 2, "\n\n", 2) == 0 ||
701 		                strncmp(text + i - 2, "\r\r", 2) == 0))) {
702 			break;
703 		}
704 	}
705 
706 	return NULL;
707 }
708 
709 /* Takes a string, truncates it where it's safe, returns the new length */
truncate_utf8(char * string,int maxlen)710 int truncate_utf8(char *string, int maxlen)
711 {
712 	char *end;
713 
714 	g_utf8_validate((const gchar *) string, maxlen, (const gchar **) &end);
715 	*end = '\0';
716 	return end - string;
717 }
718 
719 /* Parses a guint64 from string, returns TRUE on success */
parse_int64(char * string,int base,guint64 * number)720 gboolean parse_int64(char *string, int base, guint64 *number)
721 {
722 	guint64 parsed;
723 	char *endptr;
724 
725 	errno = 0;
726 	parsed = g_ascii_strtoull(string, &endptr, base);
727 	if (errno || endptr == string || *endptr != '\0') {
728 		return FALSE;
729 	}
730 	*number = parsed;
731 	return TRUE;
732 }
733 
734 /* Filters all the characters in 'blacklist' replacing them with 'replacement'.
735  * Modifies the string in-place and returns the string itself.
736  * For the opposite, use g_strcanon() */
str_reject_chars(char * string,const char * reject,char replacement)737 char *str_reject_chars(char *string, const char *reject, char replacement)
738 {
739 	char *c = string;
740 
741 	while (*c) {
742 		c += strcspn(c, reject);
743 		if (*c) {
744 			*c = replacement;
745 		}
746 	}
747 
748 	return string;
749 }
750 
751 /* Returns a string that is exactly 'char_len' utf8 characters long (not bytes),
752  * padded to the right with spaces or truncated with the 'ellipsis' parameter
753  * if specified (can be NULL).
754  * Returns a newly allocated string, or NULL on invalid parameters. */
str_pad_and_truncate(const char * string,long char_len,const char * ellipsis)755 char *str_pad_and_truncate(const char *string, long char_len, const char *ellipsis)
756 {
757 	size_t string_len = strlen(string);
758 	size_t ellipsis_len = (ellipsis) ? strlen(ellipsis) : 0;
759 	long orig_len = g_utf8_strlen(string, -1);
760 
761 	g_return_val_if_fail(char_len > ellipsis_len, NULL);
762 
763 	if (orig_len > char_len) {
764 		char *ret = g_malloc(string_len + 1);
765 		g_utf8_strncpy(ret, string, char_len - ellipsis_len);
766 		if (ellipsis) {
767 			g_strlcat(ret, ellipsis, string_len);
768 		}
769 		return ret;
770 	} else if (orig_len < char_len) {
771 		return g_strdup_printf("%s%*s", string, (int) (char_len - orig_len), "");
772 	} else {
773 		return g_strdup(string);
774 	}
775 }
776 
777 /* copied from irssi's misc.c, by timo sirainen */
b_istr_equal(gconstpointer v,gconstpointer v2)778 int b_istr_equal(gconstpointer v, gconstpointer v2)
779 {
780 	return g_ascii_strcasecmp((const char *) v, (const char *) v2) == 0;
781 }
782 
783 /* copied from irssi's misc.c, by lemonboy */
b_istr_hash(gconstpointer v)784 guint b_istr_hash(gconstpointer v)
785 {
786 	const signed char *p;
787 	guint32 h = 5381;
788 
789 	for (p = v; *p != '\0'; p++) {
790 		h = (h << 5) + h + g_ascii_toupper(*p);
791 	}
792 
793 	return h;
794 }
795