source: lib/misc.c

Last change on this file was 1bdc669, checked in by GitHub <noreply@…>, at 2023-02-23T23:48:10Z

Migrate internal users of md5.h to using GChecksum directly (#169)

  • Use GChecksum directly rather than md5 wrapper
  • Mark md5 functions as deprecated.
  • Migrate more users of md5.h to GChecksum
  • Property mode set to 100644
File size: 17.8 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 <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <ctype.h>
40#include <glib.h>
41#include <time.h>
42
43#ifdef HAVE_RESOLV_A
44#include <arpa/nameser.h>
45#include <resolv.h>
46#endif
47
48#include "ssl_client.h"
49
50void strip_linefeed(gchar *text)
51{
52        int i, j;
53        gchar *text2 = g_malloc(strlen(text) + 1);
54
55        for (i = 0, j = 0; text[i]; i++) {
56                if (text[i] != '\r') {
57                        text2[j++] = text[i];
58                }
59        }
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
107        tres = mktime(&utc);
108        res += res - tres;
109
110        /* Yes, this is a hack. And it will go wrong around DST changes.
111           BUT this is more likely to be threadsafe than messing with
112           environment variables, and possibly more portable... */
113
114        return res;
115}
116
117typedef struct htmlentity {
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        { "#38",    "&" },
151        { "#39",    "'" },
152        { "#60",    "<" },
153        { "#62",    ">" },
154        { "",        ""  }
155};
156
157void strip_html(char *in)
158{
159        char *start = in;
160        char out[strlen(in) + 1];
161        char *s = out, *cs;
162        int i, matched;
163        int taglen;
164
165        memset(out, 0, sizeof(out));
166
167        while (*in) {
168                if (*in == '<' && (g_ascii_isalpha(*(in + 1)) || *(in + 1) == '/')) {
169                        /* If in points at a < and in+1 points at a letter or a slash, this is probably
170                           a HTML-tag. Try to find a closing > and continue there. If the > can't be
171                           found, assume that it wasn't a HTML-tag after all. */
172
173                        cs = in;
174
175                        while (*in && *in != '>') {
176                                in++;
177                        }
178
179                        taglen = in - cs - 1;   /* not <0 because the above loop runs at least once */
180                        if (*in) {
181                                if (g_strncasecmp(cs + 1, "b", taglen) == 0) {
182                                        *(s++) = '\x02';
183                                } else if (g_strncasecmp(cs + 1, "/b", taglen) == 0) {
184                                        *(s++) = '\x02';
185                                } else if (g_strncasecmp(cs + 1, "i", taglen) == 0) {
186                                        *(s++) = '\x1f';
187                                } else if (g_strncasecmp(cs + 1, "/i", taglen) == 0) {
188                                        *(s++) = '\x1f';
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                                } else if (g_strncasecmp(cs + 1, "br /", taglen) == 0) {
194                                        *(s++) = '\n';
195                                }
196                                in++;
197                        } else {
198                                in = cs;
199                                *(s++) = *(in++);
200                        }
201                } else if (*in == '&') {
202                        cs = ++in;
203
204                        if (*in == '#') {
205                                in++;
206                        }
207
208                        while (*in && g_ascii_isalnum(*in)) {
209                                in++;
210                        }
211
212                        if (*in == ';') {
213                                in++;
214                        }
215                        matched = 0;
216
217                        for (i = 0; *ent[i].code; i++) {
218                                if (g_strncasecmp(ent[i].code, cs, strlen(ent[i].code)) == 0) {
219                                        int j;
220
221                                        for (j = 0; ent[i].is[j]; j++) {
222                                                *(s++) = ent[i].is[j];
223                                        }
224
225                                        matched = 1;
226                                        break;
227                                }
228                        }
229
230                        /* None of the entities were matched, so return the string */
231                        if (!matched) {
232                                in = cs - 1;
233                                *(s++) = *(in++);
234                        }
235                } else {
236                        *(s++) = *(in++);
237                }
238        }
239
240        strcpy(start, out);
241}
242
243char *escape_html(const char *html)
244{
245        const char *c = html;
246        GString *ret;
247        char *str;
248
249        if (html == NULL) {
250                return(NULL);
251        }
252
253        ret = g_string_new("");
254
255        while (*c) {
256                switch (*c) {
257                case '&':
258                        ret = g_string_append(ret, "&amp;");
259                        break;
260                case '<':
261                        ret = g_string_append(ret, "&lt;");
262                        break;
263                case '>':
264                        ret = g_string_append(ret, "&gt;");
265                        break;
266                case '"':
267                        ret = g_string_append(ret, "&quot;");
268                        break;
269                default:
270                        ret = g_string_append_c(ret, *c);
271                }
272                c++;
273        }
274
275        str = ret->str;
276        g_string_free(ret, FALSE);
277        return(str);
278}
279
280/* Decode%20a%20file%20name                                             */
281void http_decode(char *s)
282{
283        char *t;
284        int i, j, k;
285
286        t = g_new(char, strlen(s) + 1);
287
288        for (i = j = 0; s[i]; i++, j++) {
289                if (s[i] == '%') {
290                        if (sscanf(s + i + 1, "%2x", &k)) {
291                                t[j] = k;
292                                i += 2;
293                        } else {
294                                *t = 0;
295                                break;
296                        }
297                } else {
298                        t[j] = s[i];
299                }
300        }
301        t[j] = 0;
302
303        strcpy(s, t);
304        g_free(t);
305}
306
307/* Warning: This one explodes the string. Worst-cases can make the string 3x its original size! */
308/* This function is safe, but make sure you call it safely as well! */
309void http_encode(char *s)
310{
311        char t[strlen(s) + 1];
312        int i, j;
313
314        strcpy(t, s);
315        for (i = j = 0; t[i]; i++, j++) {
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                        s[j] = t[i];
322                } else {
323                        sprintf(s + j, "%%%02X", ((unsigned char *) t)[i]);
324                        j += 2;
325                }
326        }
327        s[j] = 0;
328}
329
330/* Strip newlines from a string. Modifies the string passed to it. */
331char *strip_newlines(char *source)
332{
333        int i;
334
335        for (i = 0; source[i] != '\0'; i++) {
336                if (source[i] == '\n' || source[i] == '\r') {
337                        source[i] = ' ';
338                }
339        }
340
341        return source;
342}
343
344/* Convert from one charset to another.
345
346   from_cs, to_cs: Source and destination charsets
347   src, dst: Source and destination strings
348   size: Size if src. 0 == use strlen(). strlen() is not reliable for UNICODE/UTF16 strings though.
349   maxbuf: Maximum number of bytes to write to dst
350
351   Returns the number of bytes written to maxbuf or -1 on an error.
352*/
353signed int do_iconv(char *from_cs, char *to_cs, char *src, char *dst, size_t size, size_t maxbuf)
354{
355        GIConv cd;
356        size_t res;
357        size_t inbytesleft, outbytesleft;
358        char *inbuf = src;
359        char *outbuf = dst;
360
361        cd = g_iconv_open(to_cs, from_cs);
362        if (cd == (GIConv) - 1) {
363                return -1;
364        }
365
366        inbytesleft = size ? size : strlen(src);
367        outbytesleft = maxbuf - 1;
368        res = g_iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
369        *outbuf = '\0';
370        g_iconv_close(cd);
371
372        if (res != 0) {
373                return -1;
374        } else {
375                return outbuf - dst;
376        }
377}
378
379/* A wrapper for /dev/urandom.
380 * If /dev/urandom is not present or not usable, it calls abort()
381 * to prevent bitlbee from working without a decent entropy source */
382void random_bytes(unsigned char *buf, int count)
383{
384        int fd;
385
386        if (((fd = open("/dev/urandom", O_RDONLY)) == -1) ||
387            (read(fd, buf, count) == -1)) {
388                log_message(LOGLVL_ERROR, "/dev/urandom not present - aborting");
389                abort();
390        }
391
392        close(fd);
393}
394
395int is_bool(char *value)
396{
397        if (*value == 0) {
398                return 0;
399        }
400
401        if ((g_strcasecmp(value,
402                          "true") == 0) || (g_strcasecmp(value, "yes") == 0) || (g_strcasecmp(value, "on") == 0)) {
403                return 1;
404        }
405        if ((g_strcasecmp(value,
406                          "false") == 0) || (g_strcasecmp(value, "no") == 0) || (g_strcasecmp(value, "off") == 0)) {
407                return 1;
408        }
409
410        while (*value) {
411                if (!g_ascii_isdigit(*value)) {
412                        return 0;
413                } else {
414                        value++;
415                }
416        }
417
418        return 1;
419}
420
421int bool2int(char *value)
422{
423        int i;
424
425        if ((g_strcasecmp(value,
426                          "true") == 0) || (g_strcasecmp(value, "yes") == 0) || (g_strcasecmp(value, "on") == 0)) {
427                return 1;
428        }
429        if ((g_strcasecmp(value,
430                          "false") == 0) || (g_strcasecmp(value, "no") == 0) || (g_strcasecmp(value, "off") == 0)) {
431                return 0;
432        }
433
434        if (sscanf(value, "%d", &i) == 1) {
435                return i;
436        }
437
438        return 0;
439}
440
441struct ns_srv_reply **srv_lookup(char *service, char *protocol, char *domain)
442{
443        struct ns_srv_reply **replies = NULL;
444
445#ifdef HAVE_RESOLV_A
446        struct ns_srv_reply *reply = NULL;
447        char name[1024];
448        unsigned char querybuf[1024];
449        const unsigned char *buf;
450        ns_msg nsh;
451        ns_rr rr;
452        int n, len, size;
453
454        g_snprintf(name, sizeof(name), "_%s._%s.%s", service, protocol, domain);
455
456        if ((size = res_query(name, ns_c_in, ns_t_srv, querybuf, sizeof(querybuf))) <= 0) {
457                return NULL;
458        }
459
460        if (ns_initparse(querybuf, size, &nsh) != 0) {
461                return NULL;
462        }
463
464        n = 0;
465        while (ns_parserr(&nsh, ns_s_an, n, &rr) == 0) {
466                char name[NS_MAXDNAME];
467
468                if (ns_rr_rdlen(rr) < 7) {
469                        break;
470                }
471
472                buf = ns_rr_rdata(rr);
473
474                if (dn_expand(querybuf, querybuf + size, &buf[6], name, NS_MAXDNAME) == -1) {
475                        break;
476                }
477
478                len = strlen(name) + 1;
479
480                reply = g_malloc(sizeof(struct ns_srv_reply) + len);
481                memcpy(reply->name, name, len);
482
483                reply->prio = (buf[0] << 8) | buf[1];
484                reply->weight = (buf[2] << 8) | buf[3];
485                reply->port = (buf[4] << 8) | buf[5];
486
487                n++;
488                replies = g_renew(struct ns_srv_reply *, replies, n + 1);
489                replies[n - 1] = reply;
490        }
491        if (replies) {
492                replies[n] = NULL;
493        }
494#endif
495
496        return replies;
497}
498
499void srv_free(struct ns_srv_reply **srv)
500{
501        int i;
502
503        if (srv == NULL) {
504                return;
505        }
506
507        for (i = 0; srv[i]; i++) {
508                g_free(srv[i]);
509        }
510        g_free(srv);
511}
512
513char *word_wrap(const char *msg, int line_len)
514{
515        GString *ret = g_string_sized_new(strlen(msg) + 16);
516
517        while (strlen(msg) > line_len) {
518                int i;
519
520                /* First try to find out if there's a newline already. Don't
521                   want to add more splits than necessary. */
522                for (i = line_len; i > 0 && msg[i] != '\n'; i--) {
523                        ;
524                }
525                if (msg[i] == '\n') {
526                        g_string_append_len(ret, msg, i + 1);
527                        msg += i + 1;
528                        continue;
529                }
530
531                for (i = line_len; i > 0; i--) {
532                        if (msg[i] == '-') {
533                                g_string_append_len(ret, msg, i + 1);
534                                g_string_append_c(ret, '\n');
535                                msg += i + 1;
536                                break;
537                        } else if (msg[i] == ' ') {
538                                g_string_append_len(ret, msg, i);
539                                g_string_append_c(ret, '\n');
540                                msg += i + 1;
541                                break;
542                        }
543                }
544                if (i == 0) {
545                        const char *end;
546                        size_t len;
547
548                        g_utf8_validate(msg, line_len, &end);
549
550                        len = (end != msg) ? end - msg : line_len;
551
552                        g_string_append_len(ret, msg, len);
553                        g_string_append_c(ret, '\n');
554                        msg += len;
555                }
556        }
557        g_string_append(ret, msg);
558
559        return g_string_free(ret, FALSE);
560}
561
562gboolean ssl_sockerr_again(void *ssl)
563{
564        if (ssl) {
565                return ssl_errno == SSL_AGAIN;
566        } else {
567                return sockerr_again();
568        }
569}
570
571/* Returns values: -1 == Failure (base64-decoded to something unexpected)
572                    0 == Okay
573                    1 == Password doesn't match the hash. */
574int md5_verify_password(char *password, char *hash)
575{
576        guint8 *pass_dec = NULL;
577        guint8 pass_md5[16];
578        GChecksum *md5_state;
579        gsize digest_len = MD5_HASH_SIZE;
580        int ret = -1, i;
581
582        if (base64_decode(hash, &pass_dec) == 21) {
583                md5_state = g_checksum_new(G_CHECKSUM_MD5);
584                g_checksum_update(md5_state, (guint8 *) password, strlen(password));
585                g_checksum_update(md5_state, (guint8 *) pass_dec + 16, 5);  /* Hmmm, salt! */
586                g_checksum_get_digest(md5_state, pass_md5, &digest_len);
587                g_checksum_free(md5_state);
588
589                for (i = 0; i < 16; i++) {
590                        if (pass_dec[i] != pass_md5[i]) {
591                                ret = 1;
592                                break;
593                        }
594                }
595
596                /* If we reached the end of the loop, it was a match! */
597                if (i == 16) {
598                        ret = 0;
599                }
600        }
601
602        g_free(pass_dec);
603
604        return ret;
605}
606
607/* Split commands (root-style, *not* IRC-style). Handles "quoting of"
608   white\ space in 'various ways'. Returns a NULL-terminated static
609   char** so watch out with nested use! Definitely not thread-safe. */
610char **split_command_parts(char *command, int limit)
611{
612        static char *cmd[IRC_MAX_ARGS + 1];
613        char *s, q = 0;
614        int k;
615
616        memset(cmd, 0, sizeof(cmd));
617        cmd[0] = command;
618        k = 1;
619        for (s = command; *s && k < IRC_MAX_ARGS; s++) {
620                if (*s == ' ' && !q) {
621                        *s = 0;
622                        while (*++s == ' ') {
623                                ;
624                        }
625                        if (k != limit && (*s == '"' || *s == '\'')) {
626                                q = *s;
627                                s++;
628                        }
629                        if (*s) {
630                                cmd[k++] = s;
631                                if (limit && k > limit) {
632                                        break;
633                                }
634                                s--;
635                        } else {
636                                break;
637                        }
638                } else if (*s == '\\' && ((!q && s[1]) || (q && q == s[1]))) {
639                        char *cpy;
640
641                        for (cpy = s; *cpy; cpy++) {
642                                cpy[0] = cpy[1];
643                        }
644                } else if (*s == q) {
645                        q = *s = 0;
646                }
647        }
648
649        /* Full zero-padding for easier argc checking. */
650        while (k <= IRC_MAX_ARGS) {
651                cmd[k++] = NULL;
652        }
653
654        return cmd;
655}
656
657char *get_rfc822_header(const char *text, const char *header, int len)
658{
659        int hlen = strlen(header), i;
660        const char *ret;
661
662        if (text == NULL) {
663                return NULL;
664        }
665
666        if (len == 0) {
667                len = strlen(text);
668        }
669
670        i = 0;
671        while ((i + hlen) < len) {
672                /* Maybe this is a bit over-commented, but I just hate this part... */
673                if (g_strncasecmp(text + i, header, hlen) == 0) {
674                        /* Skip to the (probable) end of the header */
675                        i += hlen;
676
677                        /* Find the first non-[: \t] character */
678                        while (i < len && (text[i] == ':' || text[i] == ' ' || text[i] == '\t')) {
679                                i++;
680                        }
681
682                        /* Make sure we're still inside the string */
683                        if (i >= len) {
684                                return(NULL);
685                        }
686
687                        /* Save the position */
688                        ret = text + i;
689
690                        /* Search for the end of this line */
691                        while (i < len && text[i] != '\r' && text[i] != '\n') {
692                                i++;
693                        }
694
695                        /* Copy the found data */
696                        return(g_strndup(ret, text + i - ret));
697                }
698
699                /* This wasn't the header we were looking for, skip to the next line. */
700                while (i < len && (text[i] != '\r' && text[i] != '\n')) {
701                        i++;
702                }
703                while (i < len && (text[i] == '\r' || text[i] == '\n')) {
704                        i++;
705                }
706
707                /* End of headers? */
708                if ((i >= 4 && strncmp(text + i - 4, "\r\n\r\n", 4) == 0) ||
709                    (i >= 2 && (strncmp(text + i - 2, "\n\n", 2) == 0 ||
710                                strncmp(text + i - 2, "\r\r", 2) == 0))) {
711                        break;
712                }
713        }
714
715        return NULL;
716}
717
718/* Takes a string, truncates it where it's safe, returns the new length */
719int truncate_utf8(char *string, int maxlen)
720{
721        char *end;
722
723        g_utf8_validate((const gchar *) string, maxlen, (const gchar **) &end);
724        *end = '\0';
725        return end - string;
726}
727
728/* Parses a guint64 from string, returns TRUE on success */
729gboolean parse_int64(char *string, int base, guint64 *number)
730{
731        guint64 parsed;
732        char *endptr;
733
734        errno = 0;
735        parsed = g_ascii_strtoull(string, &endptr, base);
736        if (errno || endptr == string || *endptr != '\0') {
737                return FALSE;
738        }
739        *number = parsed;
740        return TRUE;
741}
742
743/* Filters all the characters in 'blacklist' replacing them with 'replacement'.
744 * Modifies the string in-place and returns the string itself.
745 * For the opposite, use g_strcanon() */
746char *str_reject_chars(char *string, const char *reject, char replacement)
747{
748        char *c = string;
749
750        while (*c) {
751                c += strcspn(c, reject);
752                if (*c) {
753                        *c = replacement;
754                }
755        }
756
757        return string;
758}
759
760/* Returns a string that is exactly 'char_len' utf8 characters long (not bytes),
761 * padded to the right with spaces or truncated with the 'ellipsis' parameter
762 * if specified (can be NULL).
763 * Returns a newly allocated string, or NULL on invalid parameters. */
764char *str_pad_and_truncate(const char *string, long char_len, const char *ellipsis)
765{
766        size_t string_len = strlen(string);
767        size_t ellipsis_len = (ellipsis) ? strlen(ellipsis) : 0;
768        long orig_len = g_utf8_strlen(string, -1);
769
770        g_return_val_if_fail(char_len > ellipsis_len, NULL);
771
772        if (orig_len > char_len) {
773                char *ret = g_malloc(string_len + 1);
774                g_utf8_strncpy(ret, string, char_len - ellipsis_len);
775                if (ellipsis) {
776                        g_strlcat(ret, ellipsis, string_len);
777                }
778                return ret;
779        } else if (orig_len < char_len) {
780                return g_strdup_printf("%s%*s", string, (int) (char_len - orig_len), "");
781        } else {
782                return g_strdup(string);
783        }
784}
785
786/* copied from irssi's misc.c, by timo sirainen */
787int b_istr_equal(gconstpointer v, gconstpointer v2)
788{
789        return g_ascii_strcasecmp((const char *) v, (const char *) v2) == 0;
790}
791
792/* copied from irssi's misc.c, by lemonboy */
793guint b_istr_hash(gconstpointer v)
794{
795        const signed char *p;
796        guint32 h = 5381;
797
798        for (p = v; *p != '\0'; p++) {
799                h = (h << 5) + h + g_ascii_toupper(*p);
800        }
801
802        return h;
803}
804
805#ifdef NO_STRCASESTR
806char* strcasestr(const char* haystack, const char* needle)
807{
808        size_t haystackn = strlen(haystack);
809        size_t needlen = strlen(needle);
810
811        const char *p = haystack;
812        while (haystackn >= needlen) {
813                if (g_strncasecmp(p, needle, needlen) == 0) {
814                    return (char*) p;
815                }
816                p++;
817                haystackn--;
818        }
819        return NULL;
820}
821#endif
Note: See TracBrowser for help on using the repository browser.