war on BSD style curly braces
[citadel.git] / webcit / html2html.c
1 /*
2  * Output an HTML message, modifying it slightly to make sure it plays nice
3  * with the rest of our web framework.
4  *
5  * Copyright (c) 2005-2012 by the citadel.org team
6  *
7  * This program is open source software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License, version 3.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  */
15
16 #include "webcit.h"
17 #include "webserver.h"
18
19
20 /*
21  * Strip surrounding single or double quotes from a string.
22  */
23 void stripquotes(char *s)
24 {
25         int len;
26
27         if (!s) return;
28
29         len = strlen(s);
30         if (len < 2) return;
31
32         if ( ( (s[0] == '\"') && (s[len-1] == '\"') ) || ( (s[0] == '\'') && (s[len-1] == '\'') ) ) {
33                 s[len-1] = 0;
34                 strcpy(s, &s[1]);
35         }
36 }
37
38
39 /*
40  * Check to see if a META tag has overridden the declared MIME character set.
41  *
42  * charset              Character set name (left unchanged if we don't do anything)
43  * meta_http_equiv      Content of the "http-equiv" portion of the META tag
44  * meta_content         Content of the "content" portion of the META tag
45  */
46 void extract_charset_from_meta(char *charset, char *meta_http_equiv, char *meta_content)
47 {
48         char *ptr;
49         char buf[64];
50
51         if (!charset) return;
52         if (!meta_http_equiv) return;
53         if (!meta_content) return;
54
55
56         if (strcasecmp(meta_http_equiv, "Content-type")) return;
57
58         ptr = strchr(meta_content, ';');
59         if (!ptr) return;
60
61         safestrncpy(buf, ++ptr, sizeof buf);
62         striplt(buf);
63         if (!strncasecmp(buf, "charset=", 8)) {
64                 strcpy(charset, &buf[8]);
65
66                 /*
67                  * The brain-damaged webmail program in Microsoft Exchange declares
68                  * a charset of "unicode" when they really mean "UTF-8".  GNU iconv
69                  * treats "unicode" as an alias for "UTF-16" so we have to manually
70                  * fix this here, otherwise messages generated in Exchange webmail
71                  * show up as a big pile of weird characters.
72                  */
73                 if (!strcasecmp(charset, "unicode")) {
74                         strcpy(charset, "UTF-8");
75                 }
76
77                 /* Remove wandering punctuation */
78                 if ((ptr=strchr(charset, '\"'))) *ptr = 0;
79                 striplt(charset);
80         }
81 }
82
83
84
85 /*
86  * Sanitize and enhance an HTML message for display.
87  * Also convert weird character sets to UTF-8 if necessary.
88  * Also fixup img src="cid:..." type inline images to fetch the image
89  *
90  */
91 void output_html(const char *supplied_charset, int treat_as_wiki, int msgnum, StrBuf *Source, StrBuf *Target) {
92         char buf[SIZ];
93         char *msg;
94         char *ptr;
95         char *msgstart;
96         char *msgend;
97         StrBuf *converted_msg;
98         int buffer_length = 1;
99         int line_length = 0;
100         int content_length = 0;
101         char new_window[SIZ];
102         int brak = 0;
103         int alevel = 0;
104         int scriptlevel = 0;
105         int script_start_pos = (-1);
106         int i;
107         int linklen;
108         char charset[128];
109         StrBuf *BodyArea = NULL;
110 #ifdef HAVE_ICONV
111         iconv_t ic = (iconv_t)(-1) ;
112         char *ibuf;                   /* Buffer of characters to be converted */
113         char *obuf;                   /* Buffer for converted characters      */
114         size_t ibuflen;               /* Length of input buffer               */
115         size_t obuflen;               /* Length of output buffer              */
116         char *osav;                   /* Saved pointer to output buffer       */
117 #endif
118         if (Target == NULL)
119                 Target = WC->WBuf;
120
121         safestrncpy(charset, supplied_charset, sizeof charset);
122         msg = strdup("");
123         sprintf(new_window, "<a target=\"%s\" href=", TARGET);
124
125         if (Source == NULL) while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
126                 line_length = strlen(buf);
127                 buffer_length = content_length + line_length + 2;
128                 ptr = realloc(msg, buffer_length);
129                 if (ptr == NULL) {
130                         StrBufAppendPrintf(Target, "<b>");
131                         StrBufAppendPrintf(Target, _("realloc() error! couldn't get %d bytes: %s"),
132                                         buffer_length + 1,
133                                         strerror(errno));
134                         StrBufAppendPrintf(Target, "</b><br><br>\n");
135                         while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
136                                 /** flush */
137                         }
138                         free(msg);
139                         return;
140                 }
141                 msg = ptr;
142                 strcpy(&msg[content_length], buf);
143                 content_length += line_length;
144                 strcpy(&msg[content_length], "\n");
145                 content_length += 1;
146         }
147         else {
148                 content_length = StrLength(Source);
149                 free(msg);
150                 msg = (char*) ChrPtr(Source);/* TODO: remove cast */
151                 buffer_length = content_length;
152         }
153
154         /** Do a first pass to isolate the message body */
155         ptr = msg + 1;
156         msgstart = msg;
157         msgend = &msg[content_length];
158
159         while (ptr < msgend) {
160
161                 /** Advance to next tag */
162                 ptr = strchr(ptr, '<');
163                 if ((ptr == NULL) || (ptr >= msgend)) break;
164                 ++ptr;
165                 if ((ptr == NULL) || (ptr >= msgend)) break;
166
167                 /*
168                  *  Look for META tags.  Some messages (particularly in
169                  *  Asian locales) illegally declare a message's character
170                  *  set in the HTML instead of in the MIME headers.  This
171                  *  is wrong but we have to work around it anyway.
172                  */
173                 if (!strncasecmp(ptr, "META", 4)) {
174
175                         char *meta_start;
176                         char *meta_end;
177                         int meta_length;
178                         char *meta;
179                         char *meta_http_equiv;
180                         char *meta_content;
181                         char *spaceptr;
182
183                         meta_start = &ptr[4];
184                         meta_end = strchr(ptr, '>');
185                         if ((meta_end != NULL) && (meta_end <= msgend)) {
186                                 meta_length = meta_end - meta_start + 1;
187                                 meta = malloc(meta_length + 1);
188                                 safestrncpy(meta, meta_start, meta_length);
189                                 meta[meta_length] = 0;
190                                 striplt(meta);
191                                 if (!strncasecmp(meta, "HTTP-EQUIV=", 11)) {
192                                         meta_http_equiv = strdup(&meta[11]);
193                                         spaceptr = strchr(meta_http_equiv, ' ');
194                                         if (spaceptr != NULL) {
195                                                 *spaceptr = 0;
196                                                 meta_content = strdup(++spaceptr);
197                                                 if (!strncasecmp(meta_content, "content=", 8)) {
198                                                         strcpy(meta_content, &meta_content[8]);
199                                                         stripquotes(meta_http_equiv);
200                                                         stripquotes(meta_content);
201                                                         extract_charset_from_meta(charset,
202                                                                         meta_http_equiv, meta_content);
203                                                 }
204                                                 free(meta_content);
205                                         }
206                                         free(meta_http_equiv);
207                                 }
208                                 free(meta);
209                         }
210                 }
211
212                 /*
213                  * Any of these tags cause everything up to and including
214                  * the tag to be removed.
215                  */     
216                 if ( (!strncasecmp(ptr, "HTML", 4))
217                                 ||(!strncasecmp(ptr, "HEAD", 4))
218                                 ||(!strncasecmp(ptr, "/HEAD", 5))
219                                 ||(!strncasecmp(ptr, "BODY", 4)) ) {
220                         char *pBody = NULL;
221
222                         if (!strncasecmp(ptr, "BODY", 4)) {
223                                 pBody = ptr;
224                         }
225                         ptr = strchr(ptr, '>');
226                         if ((ptr == NULL) || (ptr >= msgend)) break;
227                         if ((pBody != NULL) && (ptr - pBody > 4)) {
228                                 char* src;
229                                 char *cid_start, *cid_end;
230
231                                 *ptr = '\0';
232                                 pBody += 4; 
233                                 while ((isspace(*pBody)) && (pBody < ptr))
234                                         pBody ++;
235                                 BodyArea = NewStrBufPlain(NULL,  ptr - pBody);
236
237                                 if (pBody < ptr) {
238                                         src = strstr(pBody, "cid:");
239                                         if (src) {
240                                                 cid_start = src + 4;
241                                                 cid_end = cid_start;
242                                                 while ((*cid_end != '"') && 
243                                                                 !isspace(*cid_end) &&
244                                                                 (cid_end < ptr))
245                                                         cid_end ++;
246
247                                                 /* copy tag and attributes up to src="cid: */
248                                                 StrBufAppendBufPlain(BodyArea, pBody, src - pBody, 0);
249
250                                                 /* add in /webcit/mimepart/<msgno>/CID/ 
251                                                    trailing / stops dumb URL filters getting excited */
252                                                 StrBufAppendPrintf(BodyArea,
253                                                                 "/webcit/mimepart/%d/",msgnum);
254                                                 StrBufAppendBufPlain(BodyArea, cid_start, cid_end - cid_start, 0);
255
256                                                 if (ptr - cid_end > 0)
257                                                         StrBufAppendBufPlain(BodyArea, 
258                                                                         cid_end + 1, 
259                                                                         ptr - cid_end, 0);
260                                         }
261                                         else 
262                                                 StrBufAppendBufPlain(BodyArea, pBody, ptr - pBody, 0);
263                                 }
264                                 *ptr = '>';
265                         }
266                         ++ptr;
267                         if ((ptr == NULL) || (ptr >= msgend)) break;
268                         msgstart = ptr;
269                 }
270
271                 /*
272                  * Any of these tags cause everything including and following
273                  * the tag to be removed.
274                  */
275                 if ( (!strncasecmp(ptr, "/HTML", 5))
276                                 ||(!strncasecmp(ptr, "/BODY", 5)) ) {
277                         --ptr;
278                         msgend = ptr;
279                         strcpy(ptr, "");
280
281                 }
282
283                 ++ptr;
284         }
285         if (msgstart > msg) {
286                 strcpy(msg, msgstart);
287         }
288
289         /* Now go through the message, parsing tags as necessary. */
290         converted_msg = NewStrBufPlain(NULL, content_length + 8192);
291
292
293         /** Convert foreign character sets to UTF-8 if necessary. */
294 #ifdef HAVE_ICONV
295         if ( (strcasecmp(charset, "us-ascii"))
296                         && (strcasecmp(charset, "UTF-8"))
297                         && (strcasecmp(charset, ""))
298            ) {
299                 syslog(LOG_DEBUG, "Converting %s to UTF-8\n", charset);
300                 ctdl_iconv_open("UTF-8", charset, &ic);
301                 if (ic == (iconv_t)(-1) ) {
302                         syslog(LOG_WARNING, "%s:%d iconv_open() failed: %s\n",
303                                         __FILE__, __LINE__, strerror(errno));
304                 }
305         }
306         if  (Source == NULL) {
307                 if (ic != (iconv_t)(-1) ) {
308                         ibuf = msg;
309                         ibuflen = content_length;
310                         obuflen = content_length + (content_length / 2) ;
311                         obuf = (char *) malloc(obuflen);
312                         osav = obuf;
313                         iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
314                         content_length = content_length + (content_length / 2) - obuflen;
315                         osav[content_length] = 0;
316                         free(msg);
317                         msg = osav;
318                         iconv_close(ic);
319                 }
320         }
321         else {
322                 if (ic != (iconv_t)(-1) ) {
323                         StrBuf *Buf = NewStrBufPlain(NULL, StrLength(Source) + 8096);;
324                         StrBufConvert(Source, Buf, &ic);
325                         FreeStrBuf(&Buf);
326                         iconv_close(ic);
327                         msg = (char*)ChrPtr(Source); /* TODO: get rid of this. */
328                 }
329         }
330
331 #endif
332
333         /*
334          *      At this point, the message has been stripped down to
335          *      only the content inside the <BODY></BODY> tags, and has
336          *      been converted to UTF-8 if it was originally in a foreign
337          *      character set.  The text is also guaranteed to be null
338          *      terminated now.
339          */
340
341         if (converted_msg == NULL) {
342                 StrBufAppendPrintf(Target, "Error %d: %s<br>%s:%d", errno, strerror(errno), __FILE__, __LINE__);
343                 goto BAIL;
344         }
345
346         if (BodyArea != NULL) {
347                 StrBufAppendBufPlain(converted_msg, HKEY("<table "), 0);  
348                 StrBufAppendBuf(converted_msg, BodyArea, 0);
349                 StrBufAppendBufPlain(converted_msg, HKEY(" width=\"100%\"><tr><td>"), 0);
350         }
351         ptr = msg;
352         msgend = strchr(msg, 0);
353         while (ptr < msgend) {
354
355                 /** Try to sanitize the html of any rogue scripts */
356                 if (!strncasecmp(ptr, "<script", 7)) {
357                         if (scriptlevel == 0) {
358                                 script_start_pos = StrLength(converted_msg);
359                         }
360                         ++scriptlevel;
361                 }
362                 if (!strncasecmp(ptr, "</script", 8)) {
363                         --scriptlevel;
364                 }
365
366                 /**
367                  * Change mailto: links to WebCit mail, by replacing the
368                  * link with one that points back to our mail room.  Due to
369                  * the way we parse URL's, it'll even handle mailto: links
370                  * that have "?subject=" in them.
371                  */
372                 if (!strncasecmp(ptr, "<a href=\"mailto:", 16)) {
373                         content_length += 64;
374                         StrBufAppendPrintf(converted_msg,
375                                         "<a href=\"display_enter?force_room=_MAIL_?recp=");
376                         ptr = &ptr[16];
377                         ++alevel;
378                         ++brak;
379                 }
380                 /** Make external links open in a separate window */
381                 else if (!strncasecmp(ptr, "<a href=\"", 9)) {
382                         ++alevel;
383                         ++brak;
384                         if ( ((strchr(ptr, ':') < strchr(ptr, '/')))
385                                         &&  ((strchr(ptr, '/') < strchr(ptr, '>'))) 
386                            ) {
387                                 /* open external links to new window */
388                                 StrBufAppendPrintf(converted_msg, new_window);
389                                 ptr = &ptr[8];
390                         }
391                         else if (
392                                 (treat_as_wiki)
393                                 && (strncasecmp(ptr, "<a href=\"wiki?", 14))
394                                 && (strncasecmp(ptr, "<a href=\"dotgoto?", 17))
395                                 && (strncasecmp(ptr, "<a href=\"knrooms?", 17))
396                         ) {
397                                 content_length += 64;
398                                 StrBufAppendPrintf(converted_msg, "<a href=\"wiki?go=");
399                                 StrBufUrlescAppend(converted_msg, WC->CurRoom.name, NULL);
400                                 StrBufAppendPrintf(converted_msg, "?page=");
401                                 ptr = &ptr[9];
402                         }
403                         else {
404                                 StrBufAppendPrintf(converted_msg, "<a href=\"");
405                                 ptr = &ptr[9];
406                         }
407                 }
408                 /** Fixup <img src="cid:... ...> to fetch the mime part */
409                 else if (!strncasecmp(ptr, "<img ", 5)) {
410                         char *cid_start, *cid_end;
411                         char* tag_end=strchr(ptr,'>');
412                         char* src;
413                         /* FIXME - handle this situation (maybe someone opened an <img cid... 
414                          * and then ended the message)
415                          */
416                         if (!tag_end) {
417                                 syslog(LOG_DEBUG, "tag_end is null and ptr is:\n");
418                                 syslog(LOG_DEBUG, "%s\n", ptr);
419                                 syslog(LOG_DEBUG, "Theoretical bytes remaining: %d\n", (int)(msgend - ptr));
420                         }
421
422                         src=strstr(ptr, "src=\"cid:");
423                         ++brak;
424
425                         if (src
426                             && isspace(*(src-1))
427                                 && tag_end
428                                 && (cid_start=strchr(src,':'))
429                                 && (cid_end=strchr(cid_start,'"'))
430                                 && (cid_end < tag_end)
431                         ) {
432                                 /* copy tag and attributes up to src="cid: */
433                                 StrBufAppendBufPlain(converted_msg, ptr, src - ptr, 0);
434                                 cid_start++;
435
436                                 /* add in /webcit/mimepart/<msgno>/CID/ 
437                                    trailing / stops dumb URL filters getting excited */
438                                 StrBufAppendPrintf(converted_msg,
439                                                 " src=\"/webcit/mimepart/%d/",msgnum);
440                                 StrBufAppendBufPlain(converted_msg, cid_start, cid_end - cid_start, 0);
441                                 StrBufAppendBufPlain(converted_msg, "/\"", -1, 0);
442
443                                 ptr = cid_end+1;
444                         }
445                         StrBufAppendBufPlain(converted_msg, ptr, tag_end - ptr, 0);
446                         ptr = tag_end;
447                 }
448
449                 /**
450                  * Turn anything that looks like a URL into a real link, as long
451                  * as it's not inside a tag already
452                  */
453                 else if ( (brak == 0) && (alevel == 0) &&
454                           ( (!strncasecmp(ptr, "http://", 7)) ||
455                             (!strncasecmp(ptr, "https://", 8)))) {
456                         /** Find the end of the link */
457                         int strlenptr;
458                         linklen = 0;
459                                 
460                         strlenptr = strlen(ptr);
461                         for (i=0; i<=strlenptr; ++i) {
462                                 if ((ptr[i]==0)
463                                     ||(isspace(ptr[i]))
464                                     ||(ptr[i]==10)
465                                     ||(ptr[i]==13)
466                                     ||(ptr[i]=='(')
467                                     ||(ptr[i]==')')
468                                     ||(ptr[i]=='<')
469                                     ||(ptr[i]=='>')
470                                     ||(ptr[i]=='[')
471                                     ||(ptr[i]==']')
472                                     ||(ptr[i]=='"')
473                                     ||(ptr[i]=='\'')
474                                         ) linklen = i;
475                                 /* did s.b. send us an entity? */
476                                 if (ptr[i] == '&') {
477                                         if ((ptr[i+2] ==';') ||
478                                             (ptr[i+3] ==';') ||
479                                             (ptr[i+5] ==';') ||
480                                             (ptr[i+6] ==';') ||
481                                             (ptr[i+7] ==';'))
482                                                 linklen = i;
483                                 }
484                                 if (linklen > 0) break;
485                         }
486                         if (linklen > 0) {
487                                 char *ltreviewptr;
488                                 char *nbspreviewptr;
489                                 char linkedchar;
490                                 int len;
491                                         
492                                 len = linklen;
493                                 linkedchar = ptr[len];
494                                 ptr[len] = '\0';
495                                 /* spot for some subject strings tinymce tends to give us. */
496                                 ltreviewptr = strchr(ptr, '<');
497                                 if (ltreviewptr != NULL) {
498                                         *ltreviewptr = '\0';
499                                         linklen = ltreviewptr - ptr;
500                                 }
501
502                                 nbspreviewptr = strstr(ptr, "&nbsp;");
503                                 if (nbspreviewptr != NULL) {
504                                         /* nbspreviewptr = '\0'; */
505                                         linklen = nbspreviewptr - ptr;
506                                 }
507                                 if (ltreviewptr != 0)
508                                         *ltreviewptr = '<';
509
510                                 ptr[len] = linkedchar;
511
512                                 content_length += (32 + linklen);
513                                 StrBufAppendPrintf(converted_msg, "%s\"", new_window);
514                                 StrBufAppendBufPlain(converted_msg, ptr, linklen, 0);
515                                 StrBufAppendPrintf(converted_msg, "\">");
516                                 StrBufAppendBufPlain(converted_msg, ptr, linklen, 0);
517                                 ptr += linklen;
518                                 StrBufAppendPrintf(converted_msg, "</A>");
519                         }
520                 }
521                 else {
522                         StrBufAppendBufPlain(converted_msg, ptr, 1, 0);
523                         ptr++;
524                 }
525
526
527                 if ((ptr >= msg) && (ptr <= msgend)) {
528                         /*
529                          * We need to know when we're inside a tag,
530                          * so we don't turn things that look like URL's into
531                          * links, when they're already links - or image sources.
532                          */
533                         if ((ptr > msg) && (*(ptr-1) == '<')) {
534                                 ++brak;
535                         }
536                         if ((ptr > msg) && (*(ptr-1) == '>')) {
537                                 --brak;
538                                 if ((scriptlevel == 0) && (script_start_pos >= 0)) {
539                                         StrBufCutRight(converted_msg, StrLength(converted_msg) - script_start_pos);
540                                         script_start_pos = (-1);
541                                 }
542                         }
543                         if (!strncasecmp(ptr, "</A>", 3)) --alevel;
544                 }
545         }
546
547         if (BodyArea != NULL) {
548                 StrBufAppendBufPlain(converted_msg, HKEY("</td></tr></table>"), 0);  
549                 FreeStrBuf(&BodyArea);
550         }
551
552         /**     uncomment these two lines to override conversion        */
553         /**     memcpy(converted_msg, msg, content_length);             */
554         /**     output_length = content_length;                         */
555
556         /** Output our big pile of markup */
557         StrBufAppendBuf(Target, converted_msg, 0);
558
559 BAIL:   /** A little trailing vertical whitespace... */
560         StrBufAppendPrintf(Target, "<br><br>\n");
561
562         /** Now give back the memory */
563         FreeStrBuf(&converted_msg);
564         if ((msg != NULL) && (Source == NULL)) free(msg);
565 }
566
567
568
569
570
571
572 /*
573  * Look for URL's embedded in a buffer and make them linkable.  We use a
574  * target window in order to keep the Citadel session in its own window.
575  */
576 void UrlizeText(StrBuf* Target, StrBuf *Source, StrBuf *WrkBuf)
577 {
578         int len, UrlLen, Offset, TrailerLen;
579         const char *start, *end, *pos;
580         
581         FlushStrBuf(Target);
582
583         start = NULL;
584         len = StrLength(Source);
585         end = ChrPtr(Source) + len;
586         for (pos = ChrPtr(Source); (pos < end) && (start == NULL); ++pos) {
587                 if (!strncasecmp(pos, "http://", 7))
588                         start = pos;
589                 else if (!strncasecmp(pos, "ftp://", 6))
590                         start = pos;
591         }
592
593         if (start == NULL) {
594                 StrBufAppendBuf(Target, Source, 0);
595                 return;
596         }
597         FlushStrBuf(WrkBuf);
598
599         for (pos = ChrPtr(Source) + len; pos > start; --pos) {
600                 if (  (!isprint(*pos))
601                    || (isspace(*pos))
602                    || (*pos == '{')
603                    || (*pos == '}')
604                    || (*pos == '|')
605                    || (*pos == '\\')
606                    || (*pos == '^')
607                    || (*pos == '[')
608                    || (*pos == ']')
609                    || (*pos == '`')
610                    || (*pos == '<')
611                    || (*pos == '>')
612                    || (*pos == '(')
613                    || (*pos == ')')
614                 ) {
615                         end = pos;
616                 }
617         }
618         
619         UrlLen = end - start;
620         StrBufAppendBufPlain(WrkBuf, start, UrlLen, 0);
621
622         Offset = start - ChrPtr(Source);
623         if (Offset != 0)
624                 StrBufAppendBufPlain(Target, ChrPtr(Source), Offset, 0);
625         StrBufAppendPrintf(Target, "%ca href=%c%s%c TARGET=%c%s%c%c%s%c/A%c",
626                            LB, QU, ChrPtr(WrkBuf), QU, QU, TARGET, 
627                            QU, RB, ChrPtr(WrkBuf), LB, RB);
628
629         TrailerLen = StrLength(Source) - (end - ChrPtr(Source));
630         if (TrailerLen > 0)
631                 StrBufAppendBufPlain(Target, end, TrailerLen, 0);
632 }
633
634
635 void url(char *buf, size_t bufsize)
636 {
637         int len, UrlLen, Offset, TrailerLen, outpos;
638         char *start, *end, *pos;
639         char urlbuf[SIZ];
640         char outbuf[SIZ];
641
642         start = NULL;
643         len = strlen(buf);
644         if (len > bufsize) {
645                 syslog(LOG_WARNING, "URL: content longer than buffer!");
646                 return;
647         }
648         end = buf + len;
649         for (pos = buf; (pos < end) && (start == NULL); ++pos) {
650                 if (!strncasecmp(pos, "http://", 7))
651                         start = pos;
652                 if (!strncasecmp(pos, "ftp://", 6))
653                         start = pos;
654         }
655
656         if (start == NULL)
657                 return;
658
659         for (pos = buf+len; pos > start; --pos) {
660                 if (  (!isprint(*pos))
661                    || (isspace(*pos))
662                    || (*pos == '{')
663                    || (*pos == '}')
664                    || (*pos == '|')
665                    || (*pos == '\\')
666                    || (*pos == '^')
667                    || (*pos == '[')
668                    || (*pos == ']')
669                    || (*pos == '`')
670                    || (*pos == '<')
671                    || (*pos == '>')
672                    || (*pos == '(')
673                    || (*pos == ')')
674                 ) {
675                         end = pos;
676                 }
677         }
678         
679         UrlLen = end - start;
680         if (UrlLen > sizeof(urlbuf)){
681                 syslog(LOG_WARNING, "URL: content longer than buffer!");
682                 return;
683         }
684         memcpy(urlbuf, start, UrlLen);
685         urlbuf[UrlLen] = '\0';
686
687         Offset = start - buf;
688         if ((Offset != 0) && (Offset < sizeof(outbuf)))
689                 memcpy(outbuf, buf, Offset);
690         outpos = snprintf(&outbuf[Offset], sizeof(outbuf) - Offset,  
691                           "%ca href=%c%s%c TARGET=%c%s%c%c%s%c/A%c",
692                           LB, QU, urlbuf, QU, QU, TARGET, QU, RB, urlbuf, LB, RB);
693         if (outpos >= sizeof(outbuf) - Offset) {
694                 syslog(LOG_WARNING, "URL: content longer than buffer!");
695                 return;
696         }
697
698         TrailerLen = len - (end - start);
699         if (TrailerLen > 0)
700                 memcpy(outbuf + Offset + outpos, end, TrailerLen);
701         if (Offset + outpos + TrailerLen > bufsize) {
702                 syslog(LOG_WARNING, "URL: content longer than buffer!");
703                 return;
704         }
705         memcpy (buf, outbuf, Offset + outpos + TrailerLen);
706         *(buf + Offset + outpos + TrailerLen) = '\0';
707 }
708