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