* CtdlWriteObject() encode in memory instead of on disk (not tested)
[citadel.git] / citadel / tools.c
1 /*
2  * $Id$
3  *
4  * Utility functions that are used by both the client and server.
5  *
6  */
7
8 #ifdef DLL_EXPORT
9 #define IN_LIBCIT
10 #endif
11
12 #include "sysdep.h"
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <ctype.h>
18 #include <string.h>
19 #include <stdarg.h>
20
21 #if TIME_WITH_SYS_TIME
22 # include <sys/time.h>
23 # include <time.h>
24 #else
25 # if HAVE_SYS_TIME_H
26 #  include <sys/time.h>
27 # else
28 #  include <time.h>
29 # endif
30 #endif
31
32 #include "tools.h"
33 #include "citadel.h"
34
35 #define TRUE  1
36 #define FALSE 0
37
38 typedef unsigned char byte;           /* Byte type */
39 static byte dtable[256];              /* base64 encode / decode table */
40
41
42 char *safestrncpy(char *dest, const char *src, size_t n)
43 {
44         if (dest == NULL || src == NULL) {
45                 fprintf(stderr, "safestrncpy: NULL argument\n");
46                 abort();
47         }
48         strncpy(dest, src, n);
49         dest[n - 1] = 0;
50         return dest;
51 }
52
53
54
55 #ifndef HAVE_STRNCASECMP
56 int strncasecmp(char *lstr, char *rstr, int len)
57 {
58         int pos = 0;
59         char lc,rc;
60         while (pos<len) {
61                 lc=tolower(lstr[pos]);
62                 rc=tolower(rstr[pos]);
63                 if ((lc==0)&&(rc==0)) return(0);
64                 if (lc<rc) return(-1);
65                 if (lc>rc) return(1);
66                 pos=pos+1;
67         }
68         return(0);
69 }
70 #endif
71
72
73
74 /*
75  * num_tokens()  -  discover number of parameters/tokens in a string
76  */
77 int num_tokens(char *source, char tok) {
78         int a;
79         int count = 1;
80
81         if (source == NULL) return(0);
82         for (a=0; a<strlen(source); ++a) {
83                 if (source[a]==tok) ++count;
84         }
85         return(count);
86 }
87
88
89 /* extract_token_fast() - a smarter string tokenizer */
90 void extract_token(char *dest, char *source, int parmnum, char separator)
91 {
92         char *d, *s;            /* dest, source */
93         int count = 0;
94
95         strcpy(dest, "");
96
97         /* Locate desired parameter */
98         s = source;
99         while (count < parmnum) {
100                 /* End of string, bail! */
101                 if (!*s) {
102                         s = NULL;
103                         break;
104                 }
105                 if (*s == separator) {
106                         count++;
107                 }
108                 s++;
109         }
110         if (!s) return;         /* Parameter not found */
111
112         for (d = dest; *s && *s != separator; s++, d++) {
113                 *d = *s;
114         }
115         *d = 0;
116 }
117
118
119 /*
120  * extract_token()  -  a smarter string tokenizer
121  */
122 void extract_token_old(char *dest, char *source, int parmnum, char separator) 
123 {
124         int i;
125         int len;
126         int curr_parm;
127
128         strcpy(dest,"");
129         len = 0;
130         curr_parm = 0;
131
132         if (strlen(source)==0) {
133                 return;
134                 }
135
136         for (i=0; i<strlen(source); ++i) {
137                 if (source[i]==separator) {
138                         ++curr_parm;
139                 }
140                 else if (curr_parm == parmnum) {
141                         dest[len+1] = 0;
142                         dest[len++] = source[i];
143                 }
144         }
145 }
146
147
148
149 /* remove_token_fast() - a tokenizer that kills, maims, and destroys fast */
150 void remove_token(char *source, int parmnum, char separator)
151 {
152         char *d, *s;            /* dest, source */
153         int count = 0;
154
155         /* Find desired parameter */
156         d = source;
157         while (count < parmnum) {
158                 /* End of string, bail! */
159                 if (!*d) {
160                         d = NULL;
161                         break;
162                 }
163                 if (*d == separator) {
164                         count++;
165                 }
166                 d++;
167         }
168         if (!d) return;         /* Parameter not found */
169
170         /* Find next parameter */
171         s = d;
172         while (*s && *s != separator) {
173                 s++;
174         }
175
176         /* Hack and slash */
177         if (*s)
178                 strcpy(d, ++s);
179         else if (d == source)
180                 *d = 0;
181         else
182                 *--d = 0;
183         /*
184         while (*s) {
185                 *d++ = *s++;
186         }
187         *d = 0;
188         */
189 }
190
191
192 /*
193  * remove_token()  -  a tokenizer that kills, maims, and destroys
194  */
195 void remove_token_old(char *source, int parmnum, char separator)
196 {
197         int i;
198         int len;
199         int curr_parm;
200         int start, end;
201
202         len = 0;
203         curr_parm = 0;
204         start = (-1);
205         end = (-1);
206
207         if (strlen(source)==0) {
208                 return;
209                 }
210
211         for (i=0; i<strlen(source); ++i) {
212                 if ( (start < 0) && (curr_parm == parmnum) ) {
213                         start = i;
214                 }
215
216                 if ( (end < 0) && (curr_parm == (parmnum+1)) ) {
217                         end = i;
218                 }
219
220                 if (source[i]==separator) {
221                         ++curr_parm;
222                 }
223         }
224
225         if (end < 0) end = strlen(source);
226         strcpy(&source[start], &source[end]);
227 }
228
229
230
231
232 /*
233  * extract_int()  -  extract an int parm w/o supplying a buffer
234  */
235 int extract_int(char *source, int parmnum)
236 {
237         char buf[SIZ];
238         
239         extract_token(buf, source, parmnum, '|');
240         return(atoi(buf));
241 }
242
243 /*
244  * extract_long()  -  extract an long parm w/o supplying a buffer
245  */
246 long extract_long(char *source, long int parmnum)
247 {
248         char buf[SIZ];
249         
250         extract_token(buf, source, parmnum, '|');
251         return(atol(buf));
252 }
253
254
255
256 /*
257  * CtdlDecodeBase64() and CtdlEncodeBase64() are adaptations of code by
258  * John Walker, found in full in the file "base64.c" included with this
259  * distribution.  We are moving in the direction of eventually discarding
260  * the separate executables, and using the ones in our code exclusively.
261  */
262
263 void CtdlEncodeBase64(char *dest, char *source, int sourcelen)
264 {
265     int i, hiteof = FALSE;
266     int spos = 0;
267     int dpos = 0;
268
269     /*  Fill dtable with character encodings.  */
270
271     for (i = 0; i < 26; i++) {
272         dtable[i] = 'A' + i;
273         dtable[26 + i] = 'a' + i;
274     }
275     for (i = 0; i < 10; i++) {
276         dtable[52 + i] = '0' + i;
277     }
278     dtable[62] = '+';
279     dtable[63] = '/';
280
281     while (!hiteof) {
282         byte igroup[3], ogroup[4];
283         int c, n;
284
285         igroup[0] = igroup[1] = igroup[2] = 0;
286         for (n = 0; n < 3; n++) {
287             if (spos >= sourcelen) {
288                 hiteof = TRUE;
289                 break;
290             }
291             c = source[spos++];
292             igroup[n] = (byte) c;
293         }
294         if (n > 0) {
295             ogroup[0] = dtable[igroup[0] >> 2];
296             ogroup[1] = dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)];
297             ogroup[2] = dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)];
298             ogroup[3] = dtable[igroup[2] & 0x3F];
299
300             /* Replace characters in output stream with "=" pad
301                characters if fewer than three characters were
302                read from the end of the input stream. */
303
304             if (n < 3) {
305                 ogroup[3] = '=';
306                 if (n < 2) {
307                     ogroup[2] = '=';
308                 }
309             }
310             for (i = 0; i < 4; i++) {
311                 dest[dpos++] = ogroup[i];
312                 dest[dpos] = 0;
313             }
314         }
315     }
316 }
317
318
319 /* 
320  * Convert base64-encoded to binary.  Returns the length of the decoded data.
321  * It will stop after reading 'length' bytes.
322  */
323 int CtdlDecodeBase64(char *dest, char *source, size_t length)
324 {
325     int i, c;
326     int dpos = 0;
327     int spos = 0;
328
329     for (i = 0; i < 255; i++) {
330         dtable[i] = 0x80;
331     }
332     for (i = 'A'; i <= 'Z'; i++) {
333         dtable[i] = 0 + (i - 'A');
334     }
335     for (i = 'a'; i <= 'z'; i++) {
336         dtable[i] = 26 + (i - 'a');
337     }
338     for (i = '0'; i <= '9'; i++) {
339         dtable[i] = 52 + (i - '0');
340     }
341     dtable['+'] = 62;
342     dtable['/'] = 63;
343     dtable['='] = 0;
344
345     /*CONSTANTCONDITION*/
346     while (TRUE) {
347         byte a[4], b[4], o[3];
348
349         for (i = 0; i < 4; i++) {
350             if (spos >= length) {
351                 return(dpos);
352             }
353             c = source[spos++];
354
355             if (c == 0) {
356                 if (i > 0) {
357                     return(dpos);
358                 }
359                 return(dpos);
360             }
361             if (dtable[c] & 0x80) {
362                 /* Ignoring errors: discard invalid character. */
363                 i--;
364                 continue;
365             }
366             a[i] = (byte) c;
367             b[i] = (byte) dtable[c];
368         }
369         o[0] = (b[0] << 2) | (b[1] >> 4);
370         o[1] = (b[1] << 4) | (b[2] >> 2);
371         o[2] = (b[2] << 6) | b[3];
372         i = a[2] == '=' ? 1 : (a[3] == '=' ? 2 : 3);
373         if (i>=1) dest[dpos++] = o[0];
374         if (i>=2) dest[dpos++] = o[1];
375         if (i>=3) dest[dpos++] = o[2];
376         dest[dpos] = 0;
377         if (i < 3) {
378             return(dpos);
379         }
380     }
381 }
382
383
384
385 /*
386  * Strip leading and trailing spaces from a string
387  */
388 void striplt(char *buf)
389 {
390         while ((strlen(buf) > 0) && (isspace(buf[0])))
391                 strcpy(buf, &buf[1]);
392         while ((strlen(buf) > 0) && (isspace(buf[strlen(buf) - 1])))
393                 buf[strlen(buf) - 1] = 0;
394 }
395
396
397
398
399
400 /* 
401  * Return the number of occurances of character ch in string st
402  */ 
403 int haschar(const char *st, int ch)
404 {
405         int a, b;
406         b = 0;
407         for (a = 0; a < strlen(st); ++a)
408                 if (st[a] == ch)
409                         ++b;
410         return (b);
411 }
412
413
414
415
416 /*
417  * Compare two strings, insensitive to case, punctuation, and non-alnum chars
418  */
419 int collapsed_strcmp(char *s1, char *s2) {
420         char *c1, *c2;
421         int i, ret, pos;
422
423         c1 = malloc(strlen(s1)+1);
424         c2 = malloc(strlen(s2)+1);
425         c1[0] = 0;
426         c2[0] = 0;
427
428         pos = 0;
429         for (i=0; i<strlen(s1); ++i) {
430                 if (isalnum(s1[i])) {
431                         c1[pos] = tolower(s1[i]);
432                         c1[++pos] = 0;
433                 }
434         }
435
436         pos = 0;
437         for (i=0; i<strlen(s2); ++i) {
438                 if (isalnum(s2[i])) {
439                         c2[pos] = tolower(s2[i]);
440                         c2[++pos] = 0;
441                 }
442         }
443
444         ret = strcmp(c1, c2);
445         free(c1);
446         free(c2);
447         return(ret);
448 }
449
450
451
452 /*
453  * Format a date/time stamp for output 
454  * seconds is whether to print the seconds
455  */
456 void fmt_date(char *buf, size_t n, time_t thetime, int seconds) {
457         struct tm *tm;
458         int hour;
459
460         char *ascmonths[] = {
461                 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
462                 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
463         };
464
465         strcpy(buf, "");
466         tm = localtime(&thetime);
467
468         hour = tm->tm_hour;
469         if (hour == 0)  hour = 12;
470         else if (hour > 12) hour = hour - 12;
471
472         if (seconds) {
473                 snprintf(buf, n, "%s %d %4d %d:%02d:%02d%s",
474                         ascmonths[tm->tm_mon],
475                         tm->tm_mday,
476                         tm->tm_year + 1900,
477                         hour,
478                         tm->tm_min,
479                         tm->tm_sec,
480                         ( (tm->tm_hour >= 12) ? "pm" : "am" )
481                 );
482         } else {
483                 snprintf(buf, n, "%s %d %4d %d:%02d%s",
484                         ascmonths[tm->tm_mon],
485                         tm->tm_mday,
486                         tm->tm_year + 1900,
487                         hour,
488                         tm->tm_min,
489                         ( (tm->tm_hour >= 12) ? "pm" : "am" )
490                 );
491         }
492 }
493
494
495
496 /*
497  * Determine whether the specified message number is contained within the
498  * specified set.
499  */
500 int is_msg_in_mset(char *mset, long msgnum) {
501         int num_sets;
502         int s;
503         char setstr[SIZ], lostr[SIZ], histr[SIZ];       /* was 1024 */
504         long lo, hi;
505
506         /*
507          * Now set it for all specified messages.
508          */
509         num_sets = num_tokens(mset, ',');
510         for (s=0; s<num_sets; ++s) {
511                 extract_token(setstr, mset, s, ',');
512
513                 extract_token(lostr, setstr, 0, ':');
514                 if (num_tokens(setstr, ':') >= 2) {
515                         extract_token(histr, setstr, 1, ':');
516                         if (!strcmp(histr, "*")) {
517                                 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
518                         }
519                 } 
520                 else {
521                         strcpy(histr, lostr);
522                 }
523                 lo = atol(lostr);
524                 hi = atol(histr);
525
526                 if ((msgnum >= lo) && (msgnum <= hi)) return(1);
527         }
528
529         return(0);
530 }
531
532
533 /*
534  * Utility function to "readline" from memory
535  * (returns new pointer)
536  */
537 char *memreadline(char *start, char *buf, int maxlen)
538 {
539         char ch;
540         char *ptr;
541         int len = 0;    /* tally our own length to avoid strlen() delays */
542
543         ptr = start;
544         memset(buf, 0, maxlen);
545
546         while (1) {
547                 ch = *ptr++;
548                 if ( (len < (maxlen - 1)) && (ch != 13) && (ch != 10) ) {
549                         buf[strlen(buf) + 1] = 0;
550                         buf[strlen(buf)] = ch;
551                         ++len;
552                 }
553                 if ((ch == 10) || (ch == 0)) {
554                         return ptr;
555                 }
556         }
557 }
558
559
560 /*
561  * Strip a boundarized substring out of a string (for example, remove
562  * parentheses and anything inside them).
563  */
564 void stripout(char *str, char leftboundary, char rightboundary) {
565         int a;
566         int lb = (-1);
567         int rb = (-1);
568
569         for (a = 0; a < strlen(str); ++a) {
570                 if (str[a] == leftboundary) lb = a;
571                 if (str[a] == rightboundary) rb = a;
572         }
573
574         if ( (lb > 0) && (rb > lb) ) {
575                 strcpy(&str[lb - 1], &str[rb + 1]);
576         }
577
578 }
579
580
581 /*
582  * Reduce a string down to a boundarized substring (for example, remove
583  * parentheses and anything outside them).
584  */
585 void stripallbut(char *str, char leftboundary, char rightboundary) {
586         int a;
587
588         for (a = 0; a < strlen(str); ++ a) {
589                 if (str[a] == leftboundary) strcpy(str, &str[a+1]);
590         }
591
592         for (a = 0; a < strlen(str); ++ a) {
593                 if (str[a] == rightboundary) str[a] = 0;
594         }
595
596 }
597
598 char *myfgets(char *s, int size, FILE *stream) {
599         char *ret = fgets(s, size, stream);
600         char *nl;
601
602         if (ret != NULL) {
603                 nl = strchr(s, '\n');
604
605                 if (nl != NULL)
606                         *nl = 0;
607         }
608
609         return ret;
610 }
611
612 /*
613  * Escape a string for feeding out as a URL.
614  * Output buffer must be big enough to handle escape expansion!
615  */
616 void urlesc(char *outbuf, char *strbuf)
617 {
618         int a, b, c;
619         char *ec = " #&;`'|*?-~<>^()[]{}$\\";
620
621         strcpy(outbuf, "");
622
623         for (a = 0; a < strlen(strbuf); ++a) {
624                 c = 0;
625                 for (b = 0; b < strlen(ec); ++b) {
626                         if (strbuf[a] == ec[b])
627                                 c = 1;
628                 }
629                 b = strlen(outbuf);
630                 if (c == 1)
631                         sprintf(&outbuf[b], "%%%02x", strbuf[a]);
632                 else
633                         sprintf(&outbuf[b], "%c", strbuf[a]);
634         }
635 }
636
637
638 /*
639  * Citadelian replacement for tmpnam()
640  */
641 char *CtdlTempFileName(char *prefix1, int prefix2) {
642         static int seq = 0;
643         static char buf[SIZ];
644
645         sprintf(buf, "/tmp/Citadel-%s-%d-%04x-%04x",
646                 prefix1,
647                 prefix2,
648                 (int)getpid(),
649                 ++seq
650         );
651         
652         return(buf);
653 }
654
655
656 /*
657  * Citadelian replacement for tmpfile()
658  */
659 FILE *CtdlTempFile(void) {
660         char filename[SIZ];
661         FILE *fp;
662
663         strcpy(filename, tmpnam(NULL));
664         fp = fopen(filename, "w+b");
665         unlink(filename);
666         return(fp);
667 }