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