source: lib/misc.c @ 23d6165

Last change on this file since 23d6165 was b40e60d, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-07-29T08:57:01Z

Fixing http_encode(): BitlBee now calls setlocale() (for nickname
transliteration to work), which changes the behaviour of isalpha() (turns
out it's not a simple macro). For HTTP-encoding, this sucks, especially
when doing OAuth (which is very picky about the way HTTP encoding is done).
This should fix problems some people were seeing with posting Twitter
messages containing accents.

  • Property mode set to 100644
File size: 14.9 KB
RevLine 
[b7d3cc34]1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
[1719464]4  * Copyright 2002-2006 Wilmer van der Gaast and others                *
[b7d3cc34]5  \********************************************************************/
6
7/*
[c88999c]8 * Various utility functions. Some are copied from Gaim to support the
9 * IM-modules, most are from BitlBee.
[b7d3cc34]10 *
11 * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
12 *                          (and possibly other members of the Gaim team)
[1719464]13 * Copyright 2002-2006 Wilmer van der Gaast <wilmer@gaast.net>
[b7d3cc34]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., 59 Temple Place,
30  Suite 330, Boston, MA  02111-1307  USA
31*/
32
33#define BITLBEE_CORE
34#include "nogaim.h"
[4e8db1c]35#include "base64.h"
[c6ca3ee]36#include "md5.h"
[b7d3cc34]37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
[dd8d4c5]40#include <ctype.h>
[b7d3cc34]41#include <glib.h>
42#include <time.h>
43
[36e9f62]44#ifdef HAVE_RESOLV_A
45#include <arpa/nameser.h>
46#include <resolv.h>
47#endif
48
[4cf80bb]49#include "md5.h"
[d52111a]50#include "ssl_client.h"
51
[b7d3cc34]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
[613cc55]70        memset(&tm, 0, sizeof(struct tm));
[b7d3cc34]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;
[613cc55]77       
[b7d3cc34]78        return mktime(&tm);
79}
80
[2e3a857]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
[b7d3cc34]116typedef struct htmlentity
117{
[51fdc45]118        char code[7];
119        char is[3];
[b7d3cc34]120} htmlentity_t;
121
[39cc341]122static const htmlentity_t ent[] =
[b7d3cc34]123{
[39cc341]124        { "lt",     "<" },
125        { "gt",     ">" },
126        { "amp",    "&" },
[b52e478]127        { "apos",   "'" },
[39cc341]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        { "",        ""  }
[b7d3cc34]151};
152
153void strip_html( char *in )
154{
155        char *start = in;
[0b3ffb1]156        char out[strlen(in)+1];
[b7d3cc34]157        char *s = out, *cs;
158        int i, matched;
159       
[0b3ffb1]160        memset( out, 0, sizeof( out ) );
[b7d3cc34]161       
162        while( *in )
163        {
164                if( *in == '<' && ( isalpha( *(in+1) ) || *(in+1) == '/' ) )
165                {
166                        /* If in points at a < and in+1 points at a letter or a slash, this is probably
167                           a HTML-tag. Try to find a closing > and continue there. If the > can't be
168                           found, assume that it wasn't a HTML-tag after all. */
169                       
170                        cs = in;
171                       
172                        while( *in && *in != '>' )
173                                in ++;
174                       
175                        if( *in )
176                        {
177                                if( g_strncasecmp( cs+1, "br", 2) == 0 )
178                                        *(s++) = '\n';
179                                in ++;
180                        }
181                        else
182                        {
183                                in = cs;
184                                *(s++) = *(in++);
185                        }
186                }
187                else if( *in == '&' )
188                {
189                        cs = ++in;
190                        while( *in && isalpha( *in ) )
191                                in ++;
192                       
193                        if( *in == ';' ) in ++;
194                        matched = 0;
195                       
196                        for( i = 0; *ent[i].code; i ++ )
197                                if( g_strncasecmp( ent[i].code, cs, strlen( ent[i].code ) ) == 0 )
198                                {
[39cc341]199                                        int j;
200                                       
201                                        for( j = 0; ent[i].is[j]; j ++ )
202                                                *(s++) = ent[i].is[j];
203                                       
[b7d3cc34]204                                        matched = 1;
205                                        break;
206                                }
207
208                        /* None of the entities were matched, so return the string */
209                        if( !matched )
210                        {
211                                in = cs - 1;
212                                *(s++) = *(in++);
213                        }
214                }
215                else
216                {
217                        *(s++) = *(in++);
218                }
219        }
220       
221        strcpy( start, out );
222}
223
224char *escape_html( const char *html )
225{
226        const char *c = html;
227        GString *ret;
228        char *str;
229       
230        if( html == NULL )
231                return( NULL );
232       
233        ret = g_string_new( "" );
234       
235        while( *c )
236        {
237                switch( *c )
238                {
239                        case '&':
240                                ret = g_string_append( ret, "&amp;" );
241                                break;
242                        case '<':
243                                ret = g_string_append( ret, "&lt;" );
244                                break;
245                        case '>':
246                                ret = g_string_append( ret, "&gt;" );
247                                break;
248                        case '"':
249                                ret = g_string_append( ret, "&quot;" );
250                                break;
251                        default:
252                                ret = g_string_append_c( ret, *c );
253                }
254                c ++;
255        }
256       
257        str = ret->str;
258        g_string_free( ret, FALSE );
259        return( str );
260}
261
[c88999c]262/* Decode%20a%20file%20name                                             */
263void http_decode( char *s )
264{
265        char *t;
266        int i, j, k;
267       
268        t = g_new( char, strlen( s ) + 1 );
269       
270        for( i = j = 0; s[i]; i ++, j ++ )
271        {
272                if( s[i] == '%' )
273                {
274                        if( sscanf( s + i + 1, "%2x", &k ) )
275                        {
276                                t[j] = k;
277                                i += 2;
278                        }
279                        else
280                        {
281                                *t = 0;
282                                break;
283                        }
284                }
285                else
286                {
287                        t[j] = s[i];
288                }
289        }
290        t[j] = 0;
291       
292        strcpy( s, t );
293        g_free( t );
294}
295
296/* Warning: This one explodes the string. Worst-cases can make the string 3x its original size! */
297/* This fuction is safe, but make sure you call it safely as well! */
298void http_encode( char *s )
299{
300        char *t;
301        int i, j;
302       
303        t = g_strdup( s );
304       
305        for( i = j = 0; t[i]; i ++, j ++ )
306        {
[b40e60d]307                /* Warning: isalnum() is locale-aware, so don't use it here! */
308                if( ( t[i] >= 'A' && t[i] <= 'Z' ) ||
309                    ( t[i] >= 'a' && t[i] <= 'z' ) ||
310                    ( t[i] >= '0' && t[i] <= '9' ) ||
311                    strchr( "._-~", t[i] ) )
[c88999c]312                {
[b40e60d]313                        s[j] = t[i];
[c88999c]314                }
315                else
316                {
[b40e60d]317                        sprintf( s + j, "%%%02X", ((unsigned char*)t)[i] );
318                        j += 2;
[c88999c]319                }
320        }
321        s[j] = 0;
322       
323        g_free( t );
324}
325
326/* Strip newlines from a string. Modifies the string passed to it. */ 
327char *strip_newlines( char *source )
328{
329        int i; 
330
331        for( i = 0; source[i] != '\0'; i ++ )
332                if( source[i] == '\n' || source[i] == '\r' )
333                        source[i] = ' ';
334       
335        return source;
336}
[2a6ca4f]337
338/* Wrap an IPv4 address into IPv6 space. Not thread-safe... */
339char *ipv6_wrap( char *src )
340{
341        static char dst[64];
342        int i;
343       
344        for( i = 0; src[i]; i ++ )
345                if( ( src[i] < '0' || src[i] > '9' ) && src[i] != '.' )
346                        break;
347       
348        /* Hmm, it's not even an IP... */
349        if( src[i] )
350                return src;
351       
352        g_snprintf( dst, sizeof( dst ), "::ffff:%s", src );
353       
354        return dst;
355}
356
357/* Unwrap an IPv4 address into IPv6 space. Thread-safe, because it's very simple. :-) */
358char *ipv6_unwrap( char *src )
359{
360        int i;
361       
362        if( g_strncasecmp( src, "::ffff:", 7 ) != 0 )
363                return src;
364       
365        for( i = 7; src[i]; i ++ )
366                if( ( src[i] < '0' || src[i] > '9' ) && src[i] != '.' )
367                        break;
368       
369        /* Hmm, it's not even an IP... */
370        if( src[i] )
371                return src;
372       
373        return ( src + 7 );
374}
[e27661d]375
376/* Convert from one charset to another.
377   
378   from_cs, to_cs: Source and destination charsets
379   src, dst: Source and destination strings
380   size: Size if src. 0 == use strlen(). strlen() is not reliable for UNICODE/UTF16 strings though.
381   maxbuf: Maximum number of bytes to write to dst
382   
383   Returns the number of bytes written to maxbuf or -1 on an error.
384*/
385signed int do_iconv( char *from_cs, char *to_cs, char *src, char *dst, size_t size, size_t maxbuf )
386{
[574af7e]387        GIConv cd;
[e27661d]388        size_t res;
389        size_t inbytesleft, outbytesleft;
390        char *inbuf = src;
391        char *outbuf = dst;
392       
[574af7e]393        cd = g_iconv_open( to_cs, from_cs );
394        if( cd == (GIConv) -1 )
[e27661d]395                return( -1 );
396       
397        inbytesleft = size ? size : strlen( src );
398        outbytesleft = maxbuf - 1;
[574af7e]399        res = g_iconv( cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft );
[e27661d]400        *outbuf = '\0';
[574af7e]401        g_iconv_close( cd );
[e27661d]402       
403        if( res == (size_t) -1 )
404                return( -1 );
405        else
406                return( outbuf - dst );
407}
408
[7f49a86]409/* A pretty reliable random number generator. Tries to use the /dev/random
410   devices first, and falls back to the random number generator from libc
411   when it fails. Opens randomizer devices with O_NONBLOCK to make sure a
412   lack of entropy won't halt BitlBee. */
413void random_bytes( unsigned char *buf, int count )
414{
415#ifndef _WIN32
416        static int use_dev = -1;
417       
418        /* Actually this probing code isn't really necessary, is it? */
419        if( use_dev == -1 )
420        {
421                if( access( "/dev/random", R_OK ) == 0 || access( "/dev/urandom", R_OK ) == 0 )
422                        use_dev = 1;
423                else
424                {
425                        use_dev = 0;
426                        srand( ( getpid() << 16 ) ^ time( NULL ) );
427                }
428        }
429       
430        if( use_dev )
431        {
432                int fd;
433               
434                /* At least on Linux, /dev/random can block if there's not
435                   enough entropy. We really don't want that, so if it can't
436                   give anything, use /dev/urandom instead. */
437                if( ( fd = open( "/dev/random", O_RDONLY | O_NONBLOCK ) ) >= 0 )
438                        if( read( fd, buf, count ) == count )
439                        {
440                                close( fd );
441                                return;
442                        }
443                close( fd );
444               
445                /* urandom isn't supposed to block at all, but just to be
446                   sure. If it blocks, we'll disable use_dev and use the libc
447                   randomizer instead. */
448                if( ( fd = open( "/dev/urandom", O_RDONLY | O_NONBLOCK ) ) >= 0 )
449                        if( read( fd, buf, count ) == count )
450                        {
451                                close( fd );
452                                return;
453                        }
454                close( fd );
455               
456                /* If /dev/random blocks once, we'll still try to use it
457                   again next time. If /dev/urandom also fails for some
458                   reason, stick with libc during this session. */
459               
460                use_dev = 0;
461                srand( ( getpid() << 16 ) ^ time( NULL ) );
462        }
463       
464        if( !use_dev )
465#endif
466        {
467                int i;
468               
469                /* Possibly the LSB of rand() isn't very random on some
470                   platforms. Seems okay on at least Linux and OSX though. */
471                for( i = 0; i < count; i ++ )
472                        buf[i] = rand() & 0xff;
473        }
474}
475
[5100caa]476int is_bool( char *value )
477{
478        if( *value == 0 )
479                return 0;
480       
481        if( ( g_strcasecmp( value, "true" ) == 0 ) || ( g_strcasecmp( value, "yes" ) == 0 ) || ( g_strcasecmp( value, "on" ) == 0 ) )
482                return 1;
483        if( ( g_strcasecmp( value, "false" ) == 0 ) || ( g_strcasecmp( value, "no" ) == 0 ) || ( g_strcasecmp( value, "off" ) == 0 ) )
484                return 1;
485       
486        while( *value )
487                if( !isdigit( *value ) )
488                        return 0;
489                else
490                        value ++;
491       
492        return 1;
493}
494
495int bool2int( char *value )
496{
497        int i;
498       
499        if( ( g_strcasecmp( value, "true" ) == 0 ) || ( g_strcasecmp( value, "yes" ) == 0 ) || ( g_strcasecmp( value, "on" ) == 0 ) )
500                return 1;
501        if( ( g_strcasecmp( value, "false" ) == 0 ) || ( g_strcasecmp( value, "no" ) == 0 ) || ( g_strcasecmp( value, "off" ) == 0 ) )
502                return 0;
503       
504        if( sscanf( value, "%d", &i ) == 1 )
505                return i;
506       
507        return 0;
508}
[36e9f62]509
510struct ns_srv_reply *srv_lookup( char *service, char *protocol, char *domain )
511{       
512        struct ns_srv_reply *reply = NULL;
513#ifdef HAVE_RESOLV_A
514        char name[1024];
515        unsigned char querybuf[1024];
516        const unsigned char *buf;
517        ns_msg nsh;
518        ns_rr rr;
519        int i, len, size;
520       
521        g_snprintf( name, sizeof( name ), "_%s._%s.%s", service, protocol, domain );
522       
523        if( ( size = res_query( name, ns_c_in, ns_t_srv, querybuf, sizeof( querybuf ) ) ) <= 0 )
524                return NULL;
525       
526        if( ns_initparse( querybuf, size, &nsh ) != 0 )
527                return NULL;
528       
529        if( ns_parserr( &nsh, ns_s_an, 0, &rr ) != 0 )
530                return NULL;
531       
532        size = ns_rr_rdlen( rr );
533        buf = ns_rr_rdata( rr );
534       
535        len = 0;
536        for( i = 6; i < size && buf[i]; i += buf[i] + 1 )
537                len += buf[i] + 1;
538       
539        if( i > size )
540                return NULL;
541       
542        reply = g_malloc( sizeof( struct ns_srv_reply ) + len );
543        memcpy( reply->name, buf + 7, len );
544       
545        for( i = buf[6]; i < len && buf[7+i]; i += buf[7+i] + 1 )
546                reply->name[i] = '.';
547       
548        if( i > len )
549        {
550                g_free( reply );
551                return NULL;
552        }
553       
554        reply->prio = ( buf[0] << 8 ) | buf[1];
555        reply->weight = ( buf[2] << 8 ) | buf[3];
556        reply->port = ( buf[4] << 8 ) | buf[5];
557#endif
558       
559        return reply;
560}
[d444c09]561
562/* Word wrapping. Yes, I know this isn't UTF-8 clean. I'm willing to take the risk. */
[c6ca3ee]563char *word_wrap( const char *msg, int line_len )
[d444c09]564{
565        GString *ret = g_string_sized_new( strlen( msg ) + 16 );
566       
567        while( strlen( msg ) > line_len )
568        {
569                int i;
570               
571                /* First try to find out if there's a newline already. Don't
572                   want to add more splits than necessary. */
573                for( i = line_len; i > 0 && msg[i] != '\n'; i -- );
574                if( msg[i] == '\n' )
575                {
576                        g_string_append_len( ret, msg, i + 1 );
577                        msg += i + 1;
578                        continue;
579                }
580               
581                for( i = line_len; i > 0; i -- )
582                {
583                        if( msg[i] == '-' )
584                        {
585                                g_string_append_len( ret, msg, i + 1 );
586                                g_string_append_c( ret, '\n' );
587                                msg += i + 1;
588                                break;
589                        }
590                        else if( msg[i] == ' ' )
591                        {
592                                g_string_append_len( ret, msg, i );
593                                g_string_append_c( ret, '\n' );
594                                msg += i + 1;
595                                break;
596                        }
597                }
598                if( i == 0 )
599                {
600                        g_string_append_len( ret, msg, line_len );
601                        g_string_append_c( ret, '\n' );
602                        msg += line_len;
603                }
604        }
605        g_string_append( ret, msg );
606       
607        return g_string_free( ret, FALSE );
608}
[d52111a]609
610gboolean ssl_sockerr_again( void *ssl )
611{
612        if( ssl )
613                return ssl_errno == SSL_AGAIN;
614        else
615                return sockerr_again();
616}
[4e8db1c]617
618/* Returns values: -1 == Failure (base64-decoded to something unexpected)
619                    0 == Okay
620                    1 == Password doesn't match the hash. */
621int md5_verify_password( char *password, char *hash )
622{
623        md5_byte_t *pass_dec = NULL;
624        md5_byte_t pass_md5[16];
625        md5_state_t md5_state;
[6a78c0e]626        int ret = -1, i;
[4e8db1c]627       
[6a78c0e]628        if( base64_decode( hash, &pass_dec ) == 21 )
[4e8db1c]629        {
630                md5_init( &md5_state );
631                md5_append( &md5_state, (md5_byte_t*) password, strlen( password ) );
632                md5_append( &md5_state, (md5_byte_t*) pass_dec + 16, 5 ); /* Hmmm, salt! */
633                md5_finish( &md5_state, pass_md5 );
634               
635                for( i = 0; i < 16; i ++ )
636                {
637                        if( pass_dec[i] != pass_md5[i] )
638                        {
639                                ret = 1;
640                                break;
641                        }
642                }
643               
644                /* If we reached the end of the loop, it was a match! */
645                if( i == 16 )
646                        ret = 0;
647        }
648       
649        g_free( pass_dec );
650
651        return ret;
652}
[24b8bbb]653
654char **split_command_parts( char *command )
655{
656        static char *cmd[IRC_MAX_ARGS+1];
657        char *s, q = 0;
658        int k;
659       
660        memset( cmd, 0, sizeof( cmd ) );
661        cmd[0] = command;
662        k = 1;
663        for( s = command; *s && k < IRC_MAX_ARGS; s ++ )
664                if( *s == ' ' && !q )
665                {
666                        *s = 0;
667                        while( *++s == ' ' );
668                        if( *s == '"' || *s == '\'' )
669                        {
670                                q = *s;
671                                s ++;
672                        }
673                        if( *s )
674                        {
675                                cmd[k++] = s;
676                                s --;
677                        }
678                        else
679                        {
680                                break;
681                        }
682                }
683                else if( *s == '\\' && ( ( !q && s[1] ) || ( q && q == s[1] ) ) )
684                {
685                        char *cpy;
686                       
687                        for( cpy = s; *cpy; cpy ++ )
688                                cpy[0] = cpy[1];
689                }
690                else if( *s == q )
691                {
692                        q = *s = 0;
693                }
[89c11e7]694       
695        /* Full zero-padding for easier argc checking. */
696        while( k <= IRC_MAX_ARGS )
697                cmd[k++] = NULL;
[24b8bbb]698       
699        return cmd;
700}
Note: See TracBrowser for help on using the repository browser.