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