]> git.ipfire.org Git - thirdparty/squid.git/blame - src/errorpage.cc
format fixes
[thirdparty/squid.git] / src / errorpage.cc
CommitLineData
4a9a952e 1
30a4f2a8 2/*
5d146f7d 3 * $Id: errorpage.cc,v 1.166 2001/08/29 14:57:34 robertc Exp $
30a4f2a8 4 *
5 * DEBUG: section 4 Error Generation
6 * AUTHOR: Duane Wessels
7 *
2b6662ba 8 * SQUID Web Proxy Cache http://www.squid-cache.org/
e25c139f 9 * ----------------------------------------------------------
30a4f2a8 10 *
2b6662ba 11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
30a4f2a8 19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
cbdec147 32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
e25c139f 33 *
30a4f2a8 34 */
1e74c110 35
fe40a877 36/*
4e3f29eb 37 * Abstract: These routines are used to generate error messages to be
38 * sent to clients. The error type is used to select between
39 * the various message formats. (formats are stored in the
40 * Config.errorDirectory)
fe40a877 41 */
42
44a47c6e 43#include "squid.h"
1e74c110 44
02922e76 45
46/* local types */
47
48typedef struct {
49 int id;
50 char *page_name;
51} ErrorDynamicPageInfo;
52
53/* local constant and vars */
54
1d803566 55/*
56 * note: hard coded error messages are not appended with %S automagically
57 * to give you more control on the format
58 */
2ac76861 59static const struct {
1afe05c5 60 int type; /* and page_id */
2ac76861 61 const char *text;
62} error_hard_text[] = {
1afe05c5 63
2ac76861 64 {
65 ERR_SQUID_SIGNATURE,
66 "\n<br clear=\"all\">\n"
67 "<hr noshade size=1>\n"
9bc73deb 68 "Generated %T by %h (%s)\n"
e32b11fc 69 "</BODY></HTML>\n"
1d803566 70 }
71};
02922e76 72
73static Stack ErrorDynamicPages;
74
75/* local prototypes */
76
2ac76861 77static const int error_hard_text_count = sizeof(error_hard_text) / sizeof(*error_hard_text);
02922e76 78static char **error_text = NULL;
79static int error_page_count = 0;
9b312a19 80
02922e76 81static char *errorTryLoadText(const char *page_name, const char *dir);
82static char *errorLoadText(const char *page_name);
1d803566 83static const char *errorFindHardText(err_type type);
c68e9c6b 84static ErrorDynamicPageInfo *errorDynamicPageInfoCreate(int id, const char *page_name);
85static void errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info);
1d803566 86static MemBuf errorBuildContent(ErrorState * err);
fe40a877 87static const char *errorConvert(char token, ErrorState * err);
9b312a19 88static CWCB errorSendComplete;
1e74c110 89
fe40a877 90/*
91 * Function: errorInitialize
92 *
1d803566 93 * Abstract: This function finds the error messages formats, and stores
fe40a877 94 * them in error_text[];
95 *
96 * Global effects:
97 * error_text[] - is modified
98 */
b8d8561b 99void
0673c0ba 100errorInitialize(void)
fa966b74 101{
728da2ee 102 err_type i;
1d803566 103 const char *text;
02922e76 104 error_page_count = ERR_MAX + ErrorDynamicPages.count;
1afe05c5 105 error_text = xcalloc(error_page_count, sizeof(char *));
728da2ee 106 for (i = ERR_NONE, i++; i < error_page_count; i++) {
1d803566 107 safe_free(error_text[i]);
108 /* hard-coded ? */
109 if ((text = errorFindHardText(i)))
110 error_text[i] = xstrdup(text);
c68e9c6b 111 else if (i < ERR_MAX) {
1afe05c5 112 /* precompiled ? */
02922e76 113 error_text[i] = errorLoadText(err_type_str[i]);
c68e9c6b 114 } else {
115 /* dynamic */
1afe05c5 116 ErrorDynamicPageInfo *info = ErrorDynamicPages.items[i - ERR_MAX];
02922e76 117 assert(info && info->id == i && info->page_name);
118 error_text[i] = errorLoadText(info->page_name);
119 }
1d803566 120 assert(error_text[i]);
121 }
122}
123
c68e9c6b 124void
125errorClean(void)
126{
127 if (error_text) {
128 int i;
129 for (i = ERR_NONE + 1; i < error_page_count; i++)
130 safe_free(error_text[i]);
131 safe_free(error_text);
132 }
133 while (ErrorDynamicPages.count)
134 errorDynamicPageInfoDestroy(stackPop(&ErrorDynamicPages));
135 error_page_count = 0;
136}
137
1d803566 138static const char *
139errorFindHardText(err_type type)
140{
141 int i;
142 for (i = 0; i < error_hard_text_count; i++)
143 if (error_hard_text[i].type == type)
144 return error_hard_text[i].text;
145 return NULL;
146}
147
148
149static char *
02922e76 150errorLoadText(const char *page_name)
1d803566 151{
152 /* test configured location */
02922e76 153 char *text = errorTryLoadText(page_name, Config.errorDirectory);
1d803566 154 /* test default location if failed */
155 if (!text && strcmp(Config.errorDirectory, DEFAULT_SQUID_ERROR_DIR))
02922e76 156 text = errorTryLoadText(page_name, DEFAULT_SQUID_ERROR_DIR);
1d803566 157 /* giving up if failed */
158 if (!text)
159 fatal("failed to find or read error text file.");
160 return text;
161}
162
163static char *
02922e76 164errorTryLoadText(const char *page_name, const char *dir)
1d803566 165{
9b312a19 166 int fd;
167 char path[MAXPATHLEN];
168 struct stat sb;
1d803566 169 char *text;
170
137ee196 171 snprintf(path, sizeof(path), "%s/%s", dir, page_name);
c4aefe96 172 fd = file_open(path, O_RDONLY | O_TEXT);
1d803566 173 if (fd < 0 || fstat(fd, &sb) < 0) {
174 debug(4, 0) ("errorTryLoadText: '%s': %s\n", path, xstrerror());
175 if (fd >= 0)
176 file_close(fd);
177 return NULL;
178 }
d5e36424 179 text = xcalloc(sb.st_size + 2 + 1, 1); /* 2 == space for %S */
1f7c9178 180 if (FD_READ_METHOD(fd, text, sb.st_size) != sb.st_size) {
1d803566 181 debug(4, 0) ("errorTryLoadText: failed to fully read: '%s': %s\n",
182 path, xstrerror());
183 xfree(text);
184 text = NULL;
1e74c110 185 }
1d803566 186 file_close(fd);
c68e9c6b 187 if (strstr(text, "%s") == NULL)
188 strcat(text, "%S"); /* add signature */
1d803566 189 return text;
6eb42cae 190}
191
02922e76 192static ErrorDynamicPageInfo *
193errorDynamicPageInfoCreate(int id, const char *page_name)
194{
195 ErrorDynamicPageInfo *info = xcalloc(1, sizeof(ErrorDynamicPageInfo));
196 info->id = id;
197 info->page_name = xstrdup(page_name);
198 return info;
199}
200
201static void
1afe05c5 202errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info)
02922e76 203{
204 assert(info);
205 xfree(info->page_name);
206 xfree(info);
207}
208
209int
210errorReservePageId(const char *page_name)
211{
1afe05c5 212 ErrorDynamicPageInfo *info =
213 errorDynamicPageInfoCreate(ERR_MAX + ErrorDynamicPages.count, page_name);
02922e76 214 stackPush(&ErrorDynamicPages, info);
215 return info->id;
216}
217
c68e9c6b 218static const char *
219errorPageName(int pageId)
53ad48e6 220{
c68e9c6b 221 if (pageId >= ERR_NONE && pageId < ERR_MAX) /* common case */
222 return err_type_str[pageId];
223 if (pageId >= ERR_MAX && pageId - ERR_MAX < ErrorDynamicPages.count)
224 return ((ErrorDynamicPageInfo *) ErrorDynamicPages.
225 items[pageId - ERR_MAX])->page_name;
226 return "ERR_UNKNOWN"; /* should not happen */
53ad48e6 227}
228
fe40a877 229/*
230 * Function: errorCon
231 *
232 * Abstract: This function creates a ErrorState object.
233 */
234ErrorState *
728da2ee 235errorCon(err_type type, http_status status)
fe40a877 236{
28c60158 237 ErrorState *err;
72711e31 238 err = cbdataAlloc(ErrorState);
1afe05c5 239 err->page_id = type; /* has to be reset manually if needed */
fe40a877 240 err->type = type;
241 err->http_status = status;
242 return err;
243}
244
245/*
246 * Function: errorAppendEntry
247 *
248 * Arguments: err - This object is destroyed after use in this function.
249 *
250 * Abstract: This function generates a error page from the info contained
4e3f29eb 251 * by 'err' and then stores the text in the specified store
252 * entry. This function should only be called by ``server
253 * side routines'' which need to communicate errors to the
79e0dc20 254 * client side. It should also be called from client_side.c
255 * because we now support persistent connections, and
256 * cannot assume that we can immediately write to the socket
257 * for an error.
fe40a877 258 */
259void
260errorAppendEntry(StoreEntry * entry, ErrorState * err)
261{
cb69b4c7 262 HttpReply *rep;
cb69b4c7 263 MemObject *mem = entry->mem_obj;
b50179a6 264 assert(mem != NULL);
4e3f29eb 265 assert(mem->inmem_hi == 0);
1ea80e98 266 if (entry->store_status != STORE_PENDING) {
267 /*
268 * If the entry is not STORE_PENDING, then no clients
269 * care about it, and we don't need to generate an
270 * error message
271 */
272 assert(EBIT_TEST(entry->flags, ENTRY_ABORTED));
273 assert(mem->nclients == 0);
274 errorStateFree(err);
275 return;
276 }
5507c7d5 277 storeLockObject(entry);
429cf70b 278 storeBuffer(entry);
cb69b4c7 279 rep = errorBuildReply(err);
1cfdbcf0 280 /* Add authentication header */
94439e4e 281 /* TODO: alter errorstate to be accel on|off aware. The 0 on the next line
282 * depends on authenticate behaviour: all schemes to date send no extra data
283 * on 407/401 responses, and do not check the accel state on 401/407 responses
284 */
721b0310 285 authenticateFixHeader(rep, err->auth_user_request, err->request, 0, 1);
cb69b4c7 286 httpReplySwapOut(rep, entry);
94439e4e 287 httpReplyAbsorb(mem->reply, rep);
ee08bdf5 288 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
429cf70b 289 storeBufferFlush(entry);
00691420 290 storeComplete(entry);
b50179a6 291 storeNegativeCache(entry);
292 storeReleaseRequest(entry);
5507c7d5 293 storeUnlockObject(entry);
fe40a877 294 errorStateFree(err);
fe40a877 295}
296
297/*
298 * Function: errorSend
299 *
300 * Arguments: err - This object is destroyed after use in this function.
301 *
302 * Abstract: This function generates a error page from the info contained
303 * by 'err' and then sends it to the client.
4e3f29eb 304 * The callback function errorSendComplete() is called after
305 * the page has been written to the client socket (fd).
306 * errorSendComplete() deallocates 'err'. We need to add
307 * 'err' to the cbdata because comm_write() requires it
308 * for all callback data pointers.
79e0dc20 309 *
310 * Note, normally errorSend() should only be called from
311 * routines in ssl.c and pass.c, where we don't have any
312 * StoreEntry's. In client_side.c we must allocate a StoreEntry
313 * for errors and use errorAppendEntry() to account for
314 * persistent/pipeline connections.
fe40a877 315 */
316void
317errorSend(int fd, ErrorState * err)
318{
cb69b4c7 319 HttpReply *rep;
fe40a877 320 debug(4, 3) ("errorSend: FD %d, err=%p\n", fd, err);
321 assert(fd >= 0);
88aad2e5 322 /*
323 * ugh, this is how we make sure error codes get back to
324 * the client side for logging and error tracking.
325 */
326 if (err->request)
327 err->request->err_type = err->type;
cb69b4c7 328 /* moved in front of errorBuildBuf @?@ */
b515fc11 329 err->flags.flag_cbdata = 1;
cb69b4c7 330 rep = errorBuildReply(err);
331 comm_write_mbuf(fd, httpReplyPack(rep), errorSendComplete, err);
332 httpReplyDestroy(rep);
fe40a877 333}
334
335/*
336 * Function: errorSendComplete
337 *
4e3f29eb 338 * Abstract: Called by commHandleWrite() after data has been written
339 * to the client socket.
fe40a877 340 *
341 * Note: If there is a callback, the callback is responsible for
342 * closeing the FD, otherwise we do it ourseves.
343 */
344static void
79a15e0a 345errorSendComplete(int fd, char *bufnotused, size_t size, int errflag, void *data)
fe40a877 346{
347 ErrorState *err = data;
348 debug(4, 3) ("errorSendComplete: FD %d, size=%d\n", fd, size);
349 if (errflag != COMM_ERR_CLOSING) {
94439e4e 350 if (err->callback) {
351 debug(4, 3) ("errorSendComplete: callback\n");
fe40a877 352 err->callback(fd, err->callback_data, size);
94439e4e 353 } else {
fe40a877 354 comm_close(fd);
94439e4e 355 debug(4, 3) ("errorSendComplete: comm_close\n");
356 }
fe40a877 357 }
358 errorStateFree(err);
fe40a877 359}
360
cb69b4c7 361void
9b312a19 362errorStateFree(ErrorState * err)
6eb42cae 363{
9b312a19 364 requestUnlink(err->request);
365 safe_free(err->redirect_url);
366 safe_free(err->url);
76a5501f 367 safe_free(err->host);
5f3c4e9a 368 safe_free(err->dnsserver_msg);
b5af8569 369 safe_free(err->request_hdrs);
9bc73deb 370 wordlistDestroy(&err->ftp.server_msg);
371 safe_free(err->ftp.request);
372 safe_free(err->ftp.reply);
94439e4e 373 if (err->auth_user_request)
374 authenticateAuthUserRequestUnlock(err->auth_user_request);
5d146f7d 375 err->auth_user_request = NULL;
28c60158 376 cbdataFree(err);
1e74c110 377}
8213067d 378
2658f489 379#define CVT_BUF_SZ 512
fe40a877 380
381/*
1d803566 382 * B - URL with FTP %2f hack x
383 * c - Squid error code x
384 * d - seconds elapsed since request received x
fe40a877 385 * e - errno x
386 * E - strerror() x
fe40a877 387 * f - FTP request line x
969c39b9 388 * F - FTP reply line x
7131112f 389 * g - FTP server message x
fe40a877 390 * h - cache hostname x
969c39b9 391 * H - server host name x
fe40a877 392 * i - client IP address x
393 * I - server IP address x
394 * L - HREF link for more info/contact x
395 * M - Request Method x
066ed5c1 396 * m - Error message returned by external Auth. x
fe40a877 397 * p - URL port # x
398 * P - Protocol x
b5af8569 399 * R - Full HTTP Request x
1d803566 400 * S - squid signature from ERR_SIGNATURE x
401 * s - caching proxy software with version x
fe40a877 402 * t - local time x
403 * T - UTC x
969c39b9 404 * U - URL without password x
405 * u - URL without password, %2f added to path x
fe40a877 406 * w - cachemgr email address x
407 * z - dns server error message x
408 */
409
410static const char *
9b312a19 411errorConvert(char token, ErrorState * err)
8213067d 412{
2658f489 413 request_t *r = err->request;
137ee196 414 static MemBuf mb = MemBufNULL;
eeb423fb 415 const char *p = NULL; /* takes priority over mb if set */
10270faa 416 int do_quote = 1;
eeb423fb 417
137ee196 418 memBufReset(&mb);
9b312a19 419 switch (token) {
8f872bb6 420 case 'B':
421 p = r ? ftpUrlWith2f(r) : "[no URL]";
422 break;
42b51993 423 case 'c':
424 assert(err->type >= ERR_NONE);
425 assert(err->type < ERR_MAX);
426 p = err_type_str[err->type];
427 break;
042461c3 428 case 'e':
137ee196 429 memBufPrintf(&mb, "%d", err->xerrno);
042461c3 430 break;
431 case 'E':
23d92c64 432 if (err->xerrno)
137ee196 433 memBufPrintf(&mb, "(%d) %s", err->xerrno, strerror(err->xerrno));
23d92c64 434 else
137ee196 435 memBufPrintf(&mb, "[No Error]");
03d7b07f 436 break;
fe40a877 437 case 'f':
438 /* FTP REQUEST LINE */
439 if (err->ftp.request)
440 p = err->ftp.request;
441 else
a8af3a35 442 p = "nothing";
fe40a877 443 break;
444 case 'F':
445 /* FTP REPLY LINE */
446 if (err->ftp.request)
447 p = err->ftp.reply;
448 else
a8af3a35 449 p = "nothing";
03d7b07f 450 break;
7131112f 451 case 'g':
452 /* FTP SERVER MESSAGE */
9bc73deb 453 wordlistCat(err->ftp.server_msg, &mb);
7131112f 454 break;
03d7b07f 455 case 'h':
137ee196 456 memBufPrintf(&mb, "%s", getMyHostname());
f787fb1e 457 break;
fe40a877 458 case 'H':
459 p = r ? r->host : "[unknown host]";
f787fb1e 460 break;
461 case 'i':
137ee196 462 memBufPrintf(&mb, "%s", inet_ntoa(err->src_addr));
f6aa1a5c 463 break;
f787fb1e 464 case 'I':
465 if (err->host) {
137ee196 466 memBufPrintf(&mb, "%s", err->host);
f787fb1e 467 } else
fe40a877 468 p = "[unknown]";
469 break;
470 case 'L':
471 if (Config.errHtmlText) {
137ee196 472 memBufPrintf(&mb, "%s", Config.errHtmlText);
fe40a877 473 } else
474 p = "[not available]";
475 break;
066ed5c1 476 case 'm':
94439e4e 477 p = authenticateAuthUserRequestMessage(err->auth_user_request) ? authenticateAuthUserRequestMessage(err->auth_user_request) : "[not available]";
066ed5c1 478 break;
fe40a877 479 case 'M':
480 p = r ? RequestMethodStr[r->method] : "[unkown method]";
481 break;
482 case 'p':
483 if (r) {
137ee196 484 memBufPrintf(&mb, "%d", (int) r->port);
fe40a877 485 } else {
486 p = "[unknown port]";
487 }
488 break;
489 case 'P':
490 p = r ? ProtocolStr[r->protocol] : "[unkown protocol]";
491 break;
b5af8569 492 case 'R':
865094d7 493 if (NULL != r) {
865094d7 494 Packer p;
ccf44862 495 memBufPrintf(&mb, "%s %s HTTP/%d.%d\n",
865094d7 496 RequestMethodStr[r->method],
497 strLen(r->urlpath) ? strBuf(r->urlpath) : "/",
ccf44862 498 r->http_ver.major, r->http_ver.minor);
865094d7 499 packerToMemInit(&p, &mb);
500 httpHeaderPackInto(&r->header, &p);
501 packerClean(&p);
865094d7 502 } else if (err->request_hdrs) {
503 p = err->request_hdrs;
504 } else {
505 p = "[no request]";
506 }
b5af8569 507 break;
1d803566 508 case 's':
509 p = full_appname_string;
510 break;
511 case 'S':
512 /* signature may contain %-escapes, recursion */
02922e76 513 if (err->page_id != ERR_SQUID_SIGNATURE) {
514 const int saved_id = err->page_id;
137ee196 515 MemBuf sign_mb;
02922e76 516 err->page_id = ERR_SQUID_SIGNATURE;
137ee196 517 sign_mb = errorBuildContent(err);
518 memBufPrintf(&mb, "%s", sign_mb.buf);
519 memBufClean(&sign_mb);
02922e76 520 err->page_id = saved_id;
10270faa 521 do_quote = 0;
1d803566 522 } else {
523 /* wow, somebody put %S into ERR_SIGNATURE, stop recursion */
524 p = "[%S]";
525 }
526 break;
fe40a877 527 case 't':
137ee196 528 memBufPrintf(&mb, "%s", mkhttpdlogtime(&squid_curtime));
f787fb1e 529 break;
f8291f8f 530 case 'T':
137ee196 531 memBufPrintf(&mb, "%s", mkrfc1123(squid_curtime));
f8291f8f 532 break;
fe40a877 533 case 'U':
b5af8569 534 p = r ? urlCanonicalClean(r) : err->url ? err->url : "[no URL]";
b9916917 535 break;
fe40a877 536 case 'w':
137ee196 537 if (Config.adminEmail)
538 memBufPrintf(&mb, "%s", Config.adminEmail);
539 else
fe40a877 540 p = "[unknown]";
541 break;
542 case 'z':
543 if (err->dnsserver_msg)
544 p = err->dnsserver_msg;
b9916917 545 else
fe40a877 546 p = "[unknown]";
b9916917 547 break;
e347f8e5 548 case '%':
549 p = "%";
550 break;
9b312a19 551 default:
6b8e7481 552 memBufPrintf(&mb, "%%%c", token);
9b312a19 553 break;
554 }
137ee196 555 if (!p)
eeb423fb 556 p = mb.buf; /* do not use mb after this assignment! */
137ee196 557 assert(p);
e102ebda 558 debug(4, 3) ("errorConvert: %%%c --> '%s'\n", token, p);
10270faa 559 if (do_quote)
560 p = html_quote(p);
9b312a19 561 return p;
8213067d 562}
e381a13d 563
cb69b4c7 564/* allocates and initializes an error response */
565HttpReply *
2ac76861 566errorBuildReply(ErrorState * err)
cb69b4c7 567{
cb69b4c7 568 HttpReply *rep = httpReplyCreate();
1d803566 569 MemBuf content = errorBuildContent(err);
ccf44862 570 http_version_t version;
cb69b4c7 571 /* no LMT for error pages; error pages expire immediately */
bffee5af 572 httpBuildVersion(&version, 1, 0);
ccf44862 573 httpReplySetHeaders(rep, version, err->http_status, NULL, "text/html", content.size, 0, squid_curtime);
fcc62502 574 /*
575 * include some information for downstream caches. Implicit
2246b732 576 * replaceable content. This isn't quite sufficient. xerrno is not
fcc62502 577 * necessarily meaningful to another system, so we really should
578 * expand it. Additionally, we should identify ourselves. Someone
579 * might want to know. Someone _will_ want to know OTOH, the first
580 * X-CACHE-MISS entry should tell us who.
581 */
2246b732 582 httpHeaderPutStrf(&rep->header, HDR_X_SQUID_ERROR, "%s %d",
c68e9c6b 583 errorPageName(err->page_id), err->xerrno);
1d21d91d 584 httpBodySet(&rep->body, &content);
585 /* do not memBufClean() the content, it was absorbed by httpBody */
cb69b4c7 586 return rep;
587}
588
1d803566 589static MemBuf
590errorBuildContent(ErrorState * err)
cb69b4c7 591{
1d803566 592 MemBuf content;
1d803566 593 const char *m;
594 const char *p;
cb69b4c7 595 const char *t;
596 assert(err != NULL);
02922e76 597 assert(err->page_id > ERR_NONE && err->page_id < error_page_count);
1d803566 598 memBufDefInit(&content);
02922e76 599 m = error_text[err->page_id];
1d803566 600 assert(m);
cb69b4c7 601 while ((p = strchr(m, '%'))) {
2ac76861 602 memBufAppend(&content, m, p - m); /* copy */
603 t = errorConvert(*++p, err); /* convert */
604 memBufPrintf(&content, "%s", t); /* copy */
605 m = p + 1; /* advance */
cb69b4c7 606 }
1d803566 607 if (*m)
2ac76861 608 memBufPrintf(&content, "%s", m); /* copy tail */
1d803566 609 assert(content.size == strlen(content.buf));
cb69b4c7 610 return content;
611}