source: lib/misc.c @ fd213fe

Last change on this file since fd213fe was fed4f76, checked in by dequis <dx@…>, at 2015-01-16T19:50:25Z

Fix UTF8 nick truncation issues

When nicks exceeded the length limit, they were cut at 24 bytes and that
sometimes left invalid utf8 at the end, which made the nick_ok()
validation fail and often broke those nicks completely.

This adds a truncate_utf8 function to cut the string at a safe place

Also, the method to deduplicate nicks when there's no more place to add
underscores was changed to add "_XX" at the end, where XX are two random
hex chars. The previous method in those cases was increasing the value
of the first character of the nick... which leads to silly and confusing
results (i.e. FacebookUser -> GacebookUser)

  • Property mode set to 100644
File size: 17.5 KB
Line 
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
52void 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        text2[j] = '\0';
61
62        strcpy(text, text2);
63        g_free(text2);
64}
65
66time_t get_time(int year, int month, int day, int hour, int min, int sec)
67{
68        struct tm tm;
69
70        memset(&tm, 0, sizeof(struct tm));
71        tm.tm_year = year - 1900;
72        tm.tm_mon = month - 1;
73        tm.tm_mday = day;
74        tm.tm_hour = hour;
75        tm.tm_min = min;
76        tm.tm_sec = sec >= 0 ? sec : time(NULL) % 60;
77       
78        return mktime(&tm);
79}
80
81time_t mktime_utc( struct tm *tp )
82{
83        struct tm utc;
84        time_t res, tres;
85       
86        tp->tm_isdst = -1;
87        res = mktime( tp );
88        /* Problem is, mktime() just gave us the GMT timestamp for the
89           given local time... While the given time WAS NOT local. So
90           we should fix this now.
91           
92           Now I could choose between messing with environment variables
93           (kludgy) or using timegm() (not portable)... Or doing the
94           following, which I actually prefer...
95           
96           tzset() may also work but in other places I actually want to
97           use local time.
98           
99           FFFFFFFFFFFFFFFFFFFFFUUUUUUUUUUUUUUUUUUUU!! */
100        gmtime_r( &res, &utc );
101        utc.tm_isdst = -1;
102        if( utc.tm_hour == tp->tm_hour && utc.tm_min == tp->tm_min )
103                /* Sweet! We're in UTC right now... */
104                return res;
105       
106        tres = mktime( &utc );
107        res += res - tres;
108       
109        /* Yes, this is a hack. And it will go wrong around DST changes.
110           BUT this is more likely to be threadsafe than messing with
111           environment variables, and possibly more portable... */
112       
113        return res;
114}
115
116typedef struct htmlentity
117{
118        char code[7];
119        char is[3];
120} htmlentity_t;
121
122static const htmlentity_t ent[] =
123{
124        { "lt",     "<" },
125        { "gt",     ">" },
126        { "amp",    "&" },
127        { "apos",   "'" },
128        { "quot",   "\"" },
129        { "aacute", "á" },
130        { "eacute", "é" },
131        { "iacute", "é" },
132        { "oacute", "ó" },
133        { "uacute", "ú" },
134        { "agrave", "à" },
135        { "egrave", "è" },
136        { "igrave", "ì" },
137        { "ograve", "ò" },
138        { "ugrave", "ù" },
139        { "acirc",  "â" },
140        { "ecirc",  "ê" },
141        { "icirc",  "î" },
142        { "ocirc",  "ô" },
143        { "ucirc",  "û" },
144        { "auml",   "ä" },
145        { "euml",   "ë" },
146        { "iuml",   "ï" },
147        { "ouml",   "ö" },
148        { "uuml",   "ü" },
149        { "nbsp",   " " },
150        { "",        ""  }
151};
152
153void strip_html( char *in )
154{
155        char *start = in;
156        char out[strlen(in)+1];
157        char *s = out, *cs;
158        int i, matched;
159        int taglen;
160       
161        memset( out, 0, sizeof( out ) );
162       
163        while( *in )
164        {
165                if( *in == '<' && ( g_ascii_isalpha( *(in+1) ) || *(in+1) == '/' ) )
166                {
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                        taglen = in - cs - 1;   /* not <0 because the above loop runs at least once */
177                        if( *in )
178                        {
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                                in ++;
190                        }
191                        else
192                        {
193                                in = cs;
194                                *(s++) = *(in++);
195                        }
196                }
197                else if( *in == '&' )
198                {
199                        cs = ++in;
200                        while( *in && g_ascii_isalpha( *in ) )
201                                in ++;
202                       
203                        if( *in == ';' ) in ++;
204                        matched = 0;
205                       
206                        for( i = 0; *ent[i].code; i ++ )
207                                if( g_strncasecmp( ent[i].code, cs, strlen( ent[i].code ) ) == 0 )
208                                {
209                                        int j;
210                                       
211                                        for( j = 0; ent[i].is[j]; j ++ )
212                                                *(s++) = ent[i].is[j];
213                                       
214                                        matched = 1;
215                                        break;
216                                }
217
218                        /* None of the entities were matched, so return the string */
219                        if( !matched )
220                        {
221                                in = cs - 1;
222                                *(s++) = *(in++);
223                        }
224                }
225                else
226                {
227                        *(s++) = *(in++);
228                }
229        }
230       
231        strcpy( start, out );
232}
233
234char *escape_html( const char *html )
235{
236        const char *c = html;
237        GString *ret;
238        char *str;
239       
240        if( html == NULL )
241                return( NULL );
242       
243        ret = g_string_new( "" );
244       
245        while( *c )
246        {
247                switch( *c )
248                {
249                        case '&':
250                                ret = g_string_append( ret, "&amp;" );
251                                break;
252                        case '<':
253                                ret = g_string_append( ret, "&lt;" );
254                                break;
255                        case '>':
256                                ret = g_string_append( ret, "&gt;" );
257                                break;
258                        case '"':
259                                ret = g_string_append( ret, "&quot;" );
260                                break;
261                        default:
262                                ret = g_string_append_c( ret, *c );
263                }
264                c ++;
265        }
266       
267        str = ret->str;
268        g_string_free( ret, FALSE );
269        return( str );
270}
271
272/* Decode%20a%20file%20name                                             */
273void http_decode( char *s )
274{
275        char *t;
276        int i, j, k;
277       
278        t = g_new( char, strlen( s ) + 1 );
279       
280        for( i = j = 0; s[i]; i ++, j ++ )
281        {
282                if( s[i] == '%' )
283                {
284                        if( sscanf( s + i + 1, "%2x", &k ) )
285                        {
286                                t[j] = k;
287                                i += 2;
288                        }
289                        else
290                        {
291                                *t = 0;
292                                break;
293                        }
294                }
295                else
296                {
297                        t[j] = s[i];
298                }
299        }
300        t[j] = 0;
301       
302        strcpy( s, t );
303        g_free( t );
304}
305
306/* Warning: This one explodes the string. Worst-cases can make the string 3x its original size! */
307/* This fuction is safe, but make sure you call it safely as well! */
308void http_encode( char *s )
309{
310        char t[strlen(s)+1];
311        int i, j;
312       
313        strcpy( t, s );
314        for( i = j = 0; t[i]; i ++, j ++ )
315        {
316                /* Warning: g_ascii_isalnum() is locale-aware, so don't use it here! */
317                if( ( t[i] >= 'A' && t[i] <= 'Z' ) ||
318                    ( t[i] >= 'a' && t[i] <= 'z' ) ||
319                    ( t[i] >= '0' && t[i] <= '9' ) ||
320                    strchr( "._-~", t[i] ) )
321                {
322                        s[j] = t[i];
323                }
324                else
325                {
326                        sprintf( s + j, "%%%02X", ((unsigned char*)t)[i] );
327                        j += 2;
328                }
329        }
330        s[j] = 0;
331}
332
333/* Strip newlines from a string. Modifies the string passed to it. */ 
334char *strip_newlines( char *source )
335{
336        int i; 
337
338        for( i = 0; source[i] != '\0'; i ++ )
339                if( source[i] == '\n' || source[i] == '\r' )
340                        source[i] = ' ';
341       
342        return source;
343}
344
345/* Wrap an IPv4 address into IPv6 space. Not thread-safe... */
346char *ipv6_wrap( char *src )
347{
348        static char dst[64];
349        int i;
350       
351        for( i = 0; src[i]; i ++ )
352                if( ( src[i] < '0' || src[i] > '9' ) && src[i] != '.' )
353                        break;
354       
355        /* Hmm, it's not even an IP... */
356        if( src[i] )
357                return src;
358       
359        g_snprintf( dst, sizeof( dst ), "::ffff:%s", src );
360       
361        return dst;
362}
363
364/* Unwrap an IPv4 address into IPv6 space. Thread-safe, because it's very simple. :-) */
365char *ipv6_unwrap( char *src )
366{
367        int i;
368       
369        if( g_strncasecmp( src, "::ffff:", 7 ) != 0 )
370                return src;
371       
372        for( i = 7; src[i]; i ++ )
373                if( ( src[i] < '0' || src[i] > '9' ) && src[i] != '.' )
374                        break;
375       
376        /* Hmm, it's not even an IP... */
377        if( src[i] )
378                return src;
379       
380        return ( src + 7 );
381}
382
383/* Convert from one charset to another.
384   
385   from_cs, to_cs: Source and destination charsets
386   src, dst: Source and destination strings
387   size: Size if src. 0 == use strlen(). strlen() is not reliable for UNICODE/UTF16 strings though.
388   maxbuf: Maximum number of bytes to write to dst
389   
390   Returns the number of bytes written to maxbuf or -1 on an error.
391*/
392signed int do_iconv( char *from_cs, char *to_cs, char *src, char *dst, size_t size, size_t maxbuf )
393{
394        GIConv cd;
395        size_t res;
396        size_t inbytesleft, outbytesleft;
397        char *inbuf = src;
398        char *outbuf = dst;
399       
400        cd = g_iconv_open( to_cs, from_cs );
401        if( cd == (GIConv) -1 )
402                return -1;
403       
404        inbytesleft = size ? size : strlen( src );
405        outbytesleft = maxbuf - 1;
406        res = g_iconv( cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft );
407        *outbuf = '\0';
408        g_iconv_close( cd );
409       
410        if( res != 0 )
411                return -1;
412        else
413                return outbuf - dst;
414}
415
416/* A pretty reliable random number generator. Tries to use the /dev/random
417   devices first, and falls back to the random number generator from libc
418   when it fails. Opens randomizer devices with O_NONBLOCK to make sure a
419   lack of entropy won't halt BitlBee. */
420void random_bytes( unsigned char *buf, int count )
421{
422        static int use_dev = -1;
423       
424        /* Actually this probing code isn't really necessary, is it? */
425        if( use_dev == -1 )
426        {
427                if( access( "/dev/random", R_OK ) == 0 || access( "/dev/urandom", R_OK ) == 0 )
428                        use_dev = 1;
429                else
430                {
431                        use_dev = 0;
432                        srand( ( getpid() << 16 ) ^ time( NULL ) );
433                }
434        }
435       
436        if( use_dev )
437        {
438                int fd;
439               
440                /* At least on Linux, /dev/random can block if there's not
441                   enough entropy. We really don't want that, so if it can't
442                   give anything, use /dev/urandom instead. */
443                if( ( fd = open( "/dev/random", O_RDONLY | O_NONBLOCK ) ) >= 0 )
444                        if( read( fd, buf, count ) == count )
445                        {
446                                close( fd );
447                                return;
448                        }
449                close( fd );
450               
451                /* urandom isn't supposed to block at all, but just to be
452                   sure. If it blocks, we'll disable use_dev and use the libc
453                   randomizer instead. */
454                if( ( fd = open( "/dev/urandom", O_RDONLY | O_NONBLOCK ) ) >= 0 )
455                        if( read( fd, buf, count ) == count )
456                        {
457                                close( fd );
458                                return;
459                        }
460                close( fd );
461               
462                /* If /dev/random blocks once, we'll still try to use it
463                   again next time. If /dev/urandom also fails for some
464                   reason, stick with libc during this session. */
465               
466                use_dev = 0;
467                srand( ( getpid() << 16 ) ^ time( NULL ) );
468        }
469       
470        if( !use_dev )
471        {
472                int i;
473               
474                /* Possibly the LSB of rand() isn't very random on some
475                   platforms. Seems okay on at least Linux and OSX though. */
476                for( i = 0; i < count; i ++ )
477                        buf[i] = rand() & 0xff;
478        }
479}
480
481int is_bool( char *value )
482{
483        if( *value == 0 )
484                return 0;
485       
486        if( ( g_strcasecmp( value, "true" ) == 0 ) || ( g_strcasecmp( value, "yes" ) == 0 ) || ( g_strcasecmp( value, "on" ) == 0 ) )
487                return 1;
488        if( ( g_strcasecmp( value, "false" ) == 0 ) || ( g_strcasecmp( value, "no" ) == 0 ) || ( g_strcasecmp( value, "off" ) == 0 ) )
489                return 1;
490       
491        while( *value )
492                if( !g_ascii_isdigit( *value ) )
493                        return 0;
494                else
495                        value ++;
496       
497        return 1;
498}
499
500int bool2int( char *value )
501{
502        int i;
503       
504        if( ( g_strcasecmp( value, "true" ) == 0 ) || ( g_strcasecmp( value, "yes" ) == 0 ) || ( g_strcasecmp( value, "on" ) == 0 ) )
505                return 1;
506        if( ( g_strcasecmp( value, "false" ) == 0 ) || ( g_strcasecmp( value, "no" ) == 0 ) || ( g_strcasecmp( value, "off" ) == 0 ) )
507                return 0;
508       
509        if( sscanf( value, "%d", &i ) == 1 )
510                return i;
511       
512        return 0;
513}
514
515struct ns_srv_reply **srv_lookup( char *service, char *protocol, char *domain )
516{       
517        struct ns_srv_reply **replies = NULL;
518#ifdef HAVE_RESOLV_A
519        struct ns_srv_reply *reply = NULL;
520        char name[1024];
521        unsigned char querybuf[1024];
522        const unsigned char *buf;
523        ns_msg nsh;
524        ns_rr rr;
525        int n, len, size;
526       
527        g_snprintf( name, sizeof( name ), "_%s._%s.%s", service, protocol, domain );
528       
529        if( ( size = res_query( name, ns_c_in, ns_t_srv, querybuf, sizeof( querybuf ) ) ) <= 0 )
530                return NULL;
531       
532        if( ns_initparse( querybuf, size, &nsh ) != 0 )
533                return NULL;
534       
535        n = 0;
536        while( ns_parserr( &nsh, ns_s_an, n, &rr ) == 0 )
537        {
538                char name[NS_MAXDNAME];
539
540                if( ns_rr_rdlen( rr ) < 7)
541                    break;
542
543                buf = ns_rr_rdata( rr );
544               
545                if( dn_expand(querybuf, querybuf + size, &buf[6], name, NS_MAXDNAME) == -1 )
546                        break;
547
548                len = strlen(name) + 1;
549               
550                reply = g_malloc( sizeof( struct ns_srv_reply ) + len );
551                memcpy( reply->name, name, len );
552               
553                reply->prio = ( buf[0] << 8 ) | buf[1];
554                reply->weight = ( buf[2] << 8 ) | buf[3];
555                reply->port = ( buf[4] << 8 ) | buf[5];
556               
557                n ++;
558                replies = g_renew( struct ns_srv_reply *, replies, n + 1 );
559                replies[n-1] = reply;
560        }
561        if( replies )
562                replies[n] = NULL;
563#endif
564       
565        return replies;
566}
567
568void srv_free( struct ns_srv_reply **srv )
569{
570        int i;
571       
572        if( srv == NULL )
573                return;
574       
575        for( i = 0; srv[i]; i ++ )
576                g_free( srv[i] );
577        g_free( srv );
578}
579
580/* Word wrapping. Yes, I know this isn't UTF-8 clean. I'm willing to take the risk. */
581char *word_wrap( const char *msg, int line_len )
582{
583        GString *ret = g_string_sized_new( strlen( msg ) + 16 );
584       
585        while( strlen( msg ) > line_len )
586        {
587                int i;
588               
589                /* First try to find out if there's a newline already. Don't
590                   want to add more splits than necessary. */
591                for( i = line_len; i > 0 && msg[i] != '\n'; i -- );
592                if( msg[i] == '\n' )
593                {
594                        g_string_append_len( ret, msg, i + 1 );
595                        msg += i + 1;
596                        continue;
597                }
598               
599                for( i = line_len; i > 0; i -- )
600                {
601                        if( msg[i] == '-' )
602                        {
603                                g_string_append_len( ret, msg, i + 1 );
604                                g_string_append_c( ret, '\n' );
605                                msg += i + 1;
606                                break;
607                        }
608                        else if( msg[i] == ' ' )
609                        {
610                                g_string_append_len( ret, msg, i );
611                                g_string_append_c( ret, '\n' );
612                                msg += i + 1;
613                                break;
614                        }
615                }
616                if( i == 0 )
617                {
618                        g_string_append_len( ret, msg, line_len );
619                        g_string_append_c( ret, '\n' );
620                        msg += line_len;
621                }
622        }
623        g_string_append( ret, msg );
624       
625        return g_string_free( ret, FALSE );
626}
627
628gboolean ssl_sockerr_again( void *ssl )
629{
630        if( ssl )
631                return ssl_errno == SSL_AGAIN;
632        else
633                return sockerr_again();
634}
635
636/* Returns values: -1 == Failure (base64-decoded to something unexpected)
637                    0 == Okay
638                    1 == Password doesn't match the hash. */
639int md5_verify_password( char *password, char *hash )
640{
641        md5_byte_t *pass_dec = NULL;
642        md5_byte_t pass_md5[16];
643        md5_state_t md5_state;
644        int ret = -1, i;
645       
646        if( base64_decode( hash, &pass_dec ) == 21 )
647        {
648                md5_init( &md5_state );
649                md5_append( &md5_state, (md5_byte_t*) password, strlen( password ) );
650                md5_append( &md5_state, (md5_byte_t*) pass_dec + 16, 5 ); /* Hmmm, salt! */
651                md5_finish( &md5_state, pass_md5 );
652               
653                for( i = 0; i < 16; i ++ )
654                {
655                        if( pass_dec[i] != pass_md5[i] )
656                        {
657                                ret = 1;
658                                break;
659                        }
660                }
661               
662                /* If we reached the end of the loop, it was a match! */
663                if( i == 16 )
664                        ret = 0;
665        }
666       
667        g_free( pass_dec );
668
669        return ret;
670}
671
672/* Split commands (root-style, *not* IRC-style). Handles "quoting of"
673   white\ space in 'various ways'. Returns a NULL-terminated static
674   char** so watch out with nested use! Definitely not thread-safe. */
675char **split_command_parts( char *command, int limit )
676{
677        static char *cmd[IRC_MAX_ARGS+1];
678        char *s, q = 0;
679        int k;
680       
681        memset( cmd, 0, sizeof( cmd ) );
682        cmd[0] = command;
683        k = 1;
684        for( s = command; *s && k < IRC_MAX_ARGS; s ++ )
685        {
686                if( *s == ' ' && !q )
687                {
688                        *s = 0;
689                        while( *++s == ' ' );
690                        if( k != limit && (*s == '"' || *s == '\'') )
691                        {
692                                q = *s;
693                                s ++;
694                        }
695                        if( *s )
696                        {
697                                cmd[k++] = s;
698                                if (limit && k > limit) {
699                                        break;
700                                }
701                                s --;
702                        }
703                        else
704                        {
705                                break;
706                        }
707                }
708                else if( *s == '\\' && ( ( !q && s[1] ) || ( q && q == s[1] ) ) )
709                {
710                        char *cpy;
711                       
712                        for( cpy = s; *cpy; cpy ++ )
713                                cpy[0] = cpy[1];
714                }
715                else if( *s == q )
716                {
717                        q = *s = 0;
718                }
719        }
720       
721        /* Full zero-padding for easier argc checking. */
722        while( k <= IRC_MAX_ARGS )
723                cmd[k++] = NULL;
724       
725        return cmd;
726}
727
728char *get_rfc822_header( const char *text, const char *header, int len )
729{
730        int hlen = strlen( header ), i;
731        const char *ret;
732       
733        if( text == NULL )
734                return NULL;
735       
736        if( len == 0 )
737                len = strlen( text );
738       
739        i = 0;
740        while( ( i + hlen ) < len )
741        {
742                /* Maybe this is a bit over-commented, but I just hate this part... */
743                if( g_strncasecmp( text + i, header, hlen ) == 0 )
744                {
745                        /* Skip to the (probable) end of the header */
746                        i += hlen;
747                       
748                        /* Find the first non-[: \t] character */
749                        while( i < len && ( text[i] == ':' || text[i] == ' ' || text[i] == '\t' ) ) i ++;
750                       
751                        /* Make sure we're still inside the string */
752                        if( i >= len ) return( NULL );
753                       
754                        /* Save the position */
755                        ret = text + i;
756                       
757                        /* Search for the end of this line */
758                        while( i < len && text[i] != '\r' && text[i] != '\n' ) i ++;
759                       
760                        /* Make sure we're still inside the string */
761                        if( i >= len ) return( NULL );
762                       
763                        /* Copy the found data */
764                        return( g_strndup( ret, text + i - ret ) );
765                }
766               
767                /* This wasn't the header we were looking for, skip to the next line. */
768                while( i < len && ( text[i] != '\r' && text[i] != '\n' ) ) i ++;
769                while( i < len && ( text[i] == '\r' || text[i] == '\n' ) ) i ++;
770               
771                /* End of headers? */
772                if( ( i >= 4 && strncmp( text + i - 4, "\r\n\r\n", 4 ) == 0 ) ||
773                    ( i >= 2 && ( strncmp( text + i - 2, "\n\n", 2 ) == 0 ||   
774                                  strncmp( text + i - 2, "\r\r", 2 ) == 0 ) ) )
775                {
776                        break;
777                }
778        }
779       
780        return NULL;
781}
782
783/* Takes a string, truncates it where it's safe, returns the new length */
784int truncate_utf8( char *string, int maxlen )
785{
786        char *end;
787        g_utf8_validate( (const gchar *) string, maxlen, (const gchar **) &end );
788        *end = '\0';
789        return end - string;
790}
Note: See TracBrowser for help on using the repository browser.