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