]> git.ipfire.org Git - thirdparty/squid.git/blob - src/format/Format.cc
Fix memory leak of lastAclData
[thirdparty/squid.git] / src / format / Format.cc
1 /*
2 * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 #include "squid.h"
10 #include "AccessLogEntry.h"
11 #include "client_side.h"
12 #include "comm/Connection.h"
13 #include "err_detail_type.h"
14 #include "errorpage.h"
15 #include "fde.h"
16 #include "format/Format.h"
17 #include "format/Quoting.h"
18 #include "format/Token.h"
19 #include "fqdncache.h"
20 #include "http/Stream.h"
21 #include "HttpRequest.h"
22 #include "MemBuf.h"
23 #include "rfc1738.h"
24 #include "security/NegotiationHistory.h"
25 #include "SquidTime.h"
26 #include "Store.h"
27 #include "tools.h"
28 #include "URL.h"
29 #if USE_OPENSSL
30 #include "ssl/ErrorDetail.h"
31 #include "ssl/ServerBump.h"
32 #endif
33
34 /// Convert a string to NULL pointer if it is ""
35 #define strOrNull(s) ((s)==NULL||(s)[0]=='\0'?NULL:(s))
36
37 Format::Format::Format(const char *n) :
38 format(NULL),
39 next(NULL)
40 {
41 name = xstrdup(n);
42 }
43
44 Format::Format::~Format()
45 {
46 // erase the list without consuming stack space
47 while (next) {
48 // unlink the next entry for deletion
49 Format *temp = next;
50 next = temp->next;
51 temp->next = NULL;
52 delete temp;
53 }
54
55 // remove locals
56 xfree(name);
57 delete format;
58 }
59
60 bool
61 Format::Format::parse(const char *def)
62 {
63 const char *cur, *eos;
64 Token *new_lt, *last_lt;
65 enum Quoting quote = LOG_QUOTE_NONE;
66
67 debugs(46, 2, HERE << "got definition '" << def << "'");
68
69 if (format) {
70 debugs(46, DBG_IMPORTANT, "WARNING: existing format for '" << name << " " << def << "'");
71 return false;
72 }
73
74 /* very inefficent parser, but who cares, this needs to be simple */
75 /* First off, let's tokenize, we'll optimize in a second pass.
76 * A token can either be a %-prefixed sequence (usually a dynamic
77 * token but it can be an escaped sequence), or a string. */
78 cur = def;
79 eos = def + strlen(def);
80 format = new_lt = last_lt = new Token;
81 cur += new_lt->parse(cur, &quote);
82
83 while (cur < eos) {
84 new_lt = new Token;
85 last_lt->next = new_lt;
86 last_lt = new_lt;
87 cur += new_lt->parse(cur, &quote);
88 }
89
90 return true;
91 }
92
93 void
94 Format::Format::dump(StoreEntry * entry, const char *directiveName, bool eol) const
95 {
96 debugs(46, 4, HERE);
97
98 // loop rather than recursing to conserve stack space.
99 for (const Format *fmt = this; fmt; fmt = fmt->next) {
100 debugs(46, 3, HERE << "Dumping format definition for " << fmt->name);
101 if (directiveName)
102 storeAppendPrintf(entry, "%s %s ", directiveName, fmt->name);
103
104 for (Token *t = fmt->format; t; t = t->next) {
105 if (t->type == LFT_STRING)
106 storeAppendPrintf(entry, "%s", t->data.string);
107 else {
108 char argbuf[256];
109 char *arg = NULL;
110 ByteCode_t type = t->type;
111
112 switch (type) {
113 /* special cases */
114
115 case LFT_STRING:
116 break;
117 #if USE_ADAPTATION
118 case LFT_ADAPTATION_LAST_HEADER_ELEM:
119 #endif
120 #if ICAP_CLIENT
121 case LFT_ICAP_REQ_HEADER_ELEM:
122 case LFT_ICAP_REP_HEADER_ELEM:
123 #endif
124 case LFT_REQUEST_HEADER_ELEM:
125 case LFT_ADAPTED_REQUEST_HEADER_ELEM:
126 case LFT_REPLY_HEADER_ELEM:
127
128 if (t->data.header.separator != ',')
129 snprintf(argbuf, sizeof(argbuf), "%s:%c%s", t->data.header.header, t->data.header.separator, t->data.header.element);
130 else
131 snprintf(argbuf, sizeof(argbuf), "%s:%s", t->data.header.header, t->data.header.element);
132
133 arg = argbuf;
134
135 switch (type) {
136 case LFT_REQUEST_HEADER_ELEM:
137 type = LFT_REQUEST_HEADER_ELEM; // XXX: remove _ELEM?
138 break;
139 case LFT_ADAPTED_REQUEST_HEADER_ELEM:
140 type = LFT_ADAPTED_REQUEST_HEADER_ELEM; // XXX: remove _ELEM?
141 break;
142 case LFT_REPLY_HEADER_ELEM:
143 type = LFT_REPLY_HEADER_ELEM; // XXX: remove _ELEM?
144 break;
145 #if USE_ADAPTATION
146 case LFT_ADAPTATION_LAST_HEADER_ELEM:
147 type = LFT_ADAPTATION_LAST_HEADER;
148 break;
149 #endif
150 #if ICAP_CLIENT
151 case LFT_ICAP_REQ_HEADER_ELEM:
152 type = LFT_ICAP_REQ_HEADER;
153 break;
154 case LFT_ICAP_REP_HEADER_ELEM:
155 type = LFT_ICAP_REP_HEADER;
156 break;
157 #endif
158 default:
159 break;
160 }
161
162 break;
163
164 case LFT_REQUEST_ALL_HEADERS:
165 case LFT_ADAPTED_REQUEST_ALL_HEADERS:
166 case LFT_REPLY_ALL_HEADERS:
167
168 #if USE_ADAPTATION
169 case LFT_ADAPTATION_LAST_ALL_HEADERS:
170 #endif
171 #if ICAP_CLIENT
172 case LFT_ICAP_REQ_ALL_HEADERS:
173 case LFT_ICAP_REP_ALL_HEADERS:
174 #endif
175
176 switch (type) {
177 case LFT_REQUEST_ALL_HEADERS:
178 type = LFT_REQUEST_HEADER;
179 break;
180 case LFT_ADAPTED_REQUEST_ALL_HEADERS:
181 type = LFT_ADAPTED_REQUEST_HEADER;
182 break;
183 case LFT_REPLY_ALL_HEADERS:
184 type = LFT_REPLY_HEADER;
185 break;
186 #if USE_ADAPTATION
187 case LFT_ADAPTATION_LAST_ALL_HEADERS:
188 type = LFT_ADAPTATION_LAST_HEADER;
189 break;
190 #endif
191 #if ICAP_CLIENT
192 case LFT_ICAP_REQ_ALL_HEADERS:
193 type = LFT_ICAP_REQ_HEADER;
194 break;
195 case LFT_ICAP_REP_ALL_HEADERS:
196 type = LFT_ICAP_REP_HEADER;
197 break;
198 #endif
199 default:
200 break;
201 }
202
203 break;
204
205 default:
206 if (t->data.string)
207 arg = t->data.string;
208
209 break;
210 }
211
212 entry->append("%", 1);
213
214 switch (t->quote) {
215
216 case LOG_QUOTE_QUOTES:
217 entry->append("\"", 1);
218 break;
219
220 case LOG_QUOTE_MIMEBLOB:
221 entry->append("[", 1);
222 break;
223
224 case LOG_QUOTE_URL:
225 entry->append("#", 1);
226 break;
227
228 case LOG_QUOTE_RAW:
229 entry->append("'", 1);
230 break;
231
232 case LOG_QUOTE_SHELL:
233 entry->append("/", 1);
234 break;
235
236 case LOG_QUOTE_NONE:
237 break;
238 }
239
240 if (t->left)
241 entry->append("-", 1);
242
243 if (t->zero)
244 entry->append("0", 1);
245
246 if (t->widthMin >= 0)
247 storeAppendPrintf(entry, "%d", t->widthMin);
248
249 if (t->widthMax >= 0)
250 storeAppendPrintf(entry, ".%d", t->widthMax);
251
252 if (arg)
253 storeAppendPrintf(entry, "{%s}", arg);
254
255 storeAppendPrintf(entry, "%s", t->label);
256
257 if (t->space)
258 entry->append(" ", 1);
259 }
260 }
261
262 if (eol)
263 entry->append("\n", 1);
264 }
265
266 }
267
268 static void
269 log_quoted_string(const char *str, char *out)
270 {
271 char *p = out;
272
273 while (*str) {
274 int l = strcspn(str, "\"\\\r\n\t");
275 memcpy(p, str, l);
276 str += l;
277 p += l;
278
279 switch (*str) {
280
281 case '\0':
282 break;
283
284 case '\r':
285 *p = '\\';
286 ++p;
287 *p = 'r';
288 ++p;
289 ++str;
290 break;
291
292 case '\n':
293 *p = '\\';
294 ++p;
295 *p = 'n';
296 ++p;
297 ++str;
298 break;
299
300 case '\t':
301 *p = '\\';
302 ++p;
303 *p = 't';
304 ++p;
305 ++str;
306 break;
307
308 default:
309 *p = '\\';
310 ++p;
311 *p = *str;
312 ++p;
313 ++str;
314 break;
315 }
316 }
317
318 *p = '\0';
319 }
320
321 #if USE_OPENSSL
322 static char *
323 sslErrorName(Ssl::ssl_error_t err, char *buf, size_t size)
324 {
325 snprintf(buf, size, "SSL_ERR=%d", err);
326 return buf;
327 }
328 #endif
329
330 void
331 Format::Format::assemble(MemBuf &mb, const AccessLogEntry::Pointer &al, int logSequenceNumber) const
332 {
333 char tmp[1024];
334 String sb;
335
336 for (Token *fmt = format; fmt != NULL; fmt = fmt->next) { /* for each token */
337 const char *out = NULL;
338 int quote = 0;
339 long int outint = 0;
340 int doint = 0;
341 int dofree = 0;
342 int64_t outoff = 0;
343 int dooff = 0;
344 struct timeval outtv = {0, 0};
345 int doMsec = 0;
346 int doSec = 0;
347
348 switch (fmt->type) {
349
350 case LFT_NONE:
351 out = "";
352 break;
353
354 case LFT_STRING:
355 out = fmt->data.string;
356 break;
357
358 case LFT_CLIENT_IP_ADDRESS:
359 al->getLogClientIp(tmp, sizeof(tmp));
360 out = tmp;
361 break;
362
363 case LFT_CLIENT_FQDN:
364 if (al->cache.caddr.isAnyAddr()) // e.g., ICAP OPTIONS lack client
365 out = "-";
366 else
367 out = fqdncache_gethostbyaddr(al->cache.caddr, FQDN_LOOKUP_IF_MISS);
368 if (!out) {
369 out = al->cache.caddr.toStr(tmp,1024);
370 }
371
372 break;
373
374 case LFT_CLIENT_PORT:
375 if (al->request) {
376 outint = al->request->client_addr.port();
377 doint = 1;
378 }
379 break;
380
381 case LFT_CLIENT_EUI:
382 #if USE_SQUID_EUI
383 // TODO make the ACL checklist have a direct link to any TCP details.
384 if (al->request && al->request->clientConnectionManager.valid() && al->request->clientConnectionManager->clientConnection != NULL) {
385 if (al->request->clientConnectionManager->clientConnection->remote.isIPv4())
386 al->request->clientConnectionManager->clientConnection->remoteEui48.encode(tmp, 1024);
387 else
388 al->request->clientConnectionManager->clientConnection->remoteEui64.encode(tmp, 1024);
389 out = tmp;
390 }
391 #endif
392 break;
393
394 case LFT_EXT_ACL_CLIENT_EUI48:
395 #if USE_SQUID_EUI
396 if (al->request && al->request->clientConnectionManager.valid() &&
397 al->request->clientConnectionManager->clientConnection != NULL &&
398 al->request->clientConnectionManager->clientConnection->remote.isIPv4()) {
399 al->request->clientConnectionManager->clientConnection->remoteEui48.encode(tmp, 1024);
400 out = tmp;
401 }
402 #endif
403 break;
404
405 case LFT_EXT_ACL_CLIENT_EUI64:
406 #if USE_SQUID_EUI
407 if (al->request && al->request->clientConnectionManager.valid() &&
408 al->request->clientConnectionManager->clientConnection != NULL &&
409 !al->request->clientConnectionManager->clientConnection->remote.isIPv4()) {
410 al->request->clientConnectionManager->clientConnection->remoteEui64.encode(tmp, 1024);
411 out = tmp;
412 }
413 #endif
414 break;
415
416 case LFT_SERVER_IP_ADDRESS:
417 if (al->hier.tcpServer != NULL) {
418 out = al->hier.tcpServer->remote.toStr(tmp,sizeof(tmp));
419 }
420 break;
421
422 case LFT_SERVER_FQDN_OR_PEER_NAME:
423 out = al->hier.host;
424 break;
425
426 case LFT_SERVER_PORT:
427 if (al->hier.tcpServer != NULL) {
428 outint = al->hier.tcpServer->remote.port();
429 doint = 1;
430 }
431 break;
432
433 case LFT_LOCAL_LISTENING_IP: {
434 // avoid logging a dash if we have reliable info
435 const bool interceptedAtKnownPort = al->request ?
436 (al->request->flags.interceptTproxy ||
437 al->request->flags.intercepted) && al->cache.port != NULL :
438 false;
439 if (interceptedAtKnownPort) {
440 const bool portAddressConfigured = !al->cache.port->s.isAnyAddr();
441 if (portAddressConfigured)
442 out = al->cache.port->s.toStr(tmp, sizeof(tmp));
443 } else if (al->tcpClient != NULL)
444 out = al->tcpClient->local.toStr(tmp, sizeof(tmp));
445 }
446 break;
447
448 case LFT_CLIENT_LOCAL_IP:
449 if (al->tcpClient != NULL) {
450 out = al->tcpClient->local.toStr(tmp,sizeof(tmp));
451 }
452 break;
453
454 case LFT_CLIENT_LOCAL_TOS:
455 if (al->tcpClient != NULL) {
456 snprintf(tmp, sizeof(tmp), "0x%x", (uint32_t)al->tcpClient->tos);
457 out = tmp;
458 }
459 break;
460
461 case LFT_CLIENT_LOCAL_NFMARK:
462 if (al->tcpClient != NULL) {
463 snprintf(tmp, sizeof(tmp), "0x%x", al->tcpClient->nfmark);
464 out = tmp;
465 }
466 break;
467
468 case LFT_LOCAL_LISTENING_PORT:
469 if (al->cache.port != NULL) {
470 outint = al->cache.port->s.port();
471 doint = 1;
472 } else if (al->request) {
473 outint = al->request->my_addr.port();
474 doint = 1;
475 }
476 break;
477
478 case LFT_CLIENT_LOCAL_PORT:
479 if (al->tcpClient != NULL) {
480 outint = al->tcpClient->local.port();
481 doint = 1;
482 }
483 break;
484
485 case LFT_SERVER_LOCAL_IP_OLD_27:
486 case LFT_SERVER_LOCAL_IP:
487 if (al->hier.tcpServer != NULL) {
488 out = al->hier.tcpServer->local.toStr(tmp,sizeof(tmp));
489 }
490 break;
491
492 case LFT_SERVER_LOCAL_PORT:
493 if (al->hier.tcpServer != NULL) {
494 outint = al->hier.tcpServer->local.port();
495 doint = 1;
496 }
497
498 break;
499
500 case LFT_SERVER_LOCAL_TOS:
501 if (al->hier.tcpServer != NULL) {
502 snprintf(tmp, sizeof(tmp), "0x%x", (uint32_t)al->hier.tcpServer->tos);
503 out = tmp;
504 }
505 break;
506
507 case LFT_SERVER_LOCAL_NFMARK:
508 if (al->hier.tcpServer != NULL) {
509 snprintf(tmp, sizeof(tmp), "0x%x", al->hier.tcpServer->nfmark);
510 out = tmp;
511 }
512 break;
513
514 case LFT_TIME_SECONDS_SINCE_EPOCH:
515 // some platforms store time in 32-bit, some 64-bit...
516 outoff = static_cast<int64_t>(current_time.tv_sec);
517 dooff = 1;
518 break;
519
520 case LFT_TIME_SUBSECOND:
521 outint = current_time.tv_usec / fmt->divisor;
522 doint = 1;
523 break;
524
525 case LFT_TIME_LOCALTIME:
526
527 case LFT_TIME_GMT: {
528 const char *spec;
529
530 struct tm *t;
531 spec = fmt->data.string;
532
533 if (fmt->type == LFT_TIME_LOCALTIME) {
534 if (!spec)
535 spec = "%d/%b/%Y:%H:%M:%S %z";
536 t = localtime(&squid_curtime);
537 } else {
538 if (!spec)
539 spec = "%d/%b/%Y:%H:%M:%S";
540
541 t = gmtime(&squid_curtime);
542 }
543
544 strftime(tmp, sizeof(tmp), spec, t);
545
546 out = tmp;
547 }
548
549 break;
550
551 case LFT_TIME_START:
552 outtv = al->cache.start_time;
553 doSec = 1;
554 break;
555
556 case LFT_TIME_TO_HANDLE_REQUEST:
557 outtv = al->cache.trTime;
558 doMsec = 1;
559 break;
560
561 case LFT_PEER_RESPONSE_TIME:
562 if (al->hier.peer_response_time.tv_sec == -1) {
563 out = "-";
564 } else {
565 outtv = al->hier.peer_response_time;
566 doMsec = 1;
567 }
568 break;
569
570 case LFT_TOTAL_SERVER_SIDE_RESPONSE_TIME: {
571 timeval total_response_time;
572 al->hier.totalResponseTime(total_response_time);
573 if (total_response_time.tv_sec == -1) {
574 out = "-";
575 } else {
576 outtv = total_response_time;
577 doMsec = 1;
578 }
579 }
580 break;
581
582 case LFT_DNS_WAIT_TIME:
583 if (al->request && al->request->dnsWait >= 0) {
584 // TODO: microsecond precision for dns wait time.
585 // Convert miliseconds to timeval struct:
586 outtv.tv_sec = al->request->dnsWait / 1000;
587 outtv.tv_usec = (al->request->dnsWait % 1000) * 1000;
588 doMsec = 1;
589 }
590 break;
591
592 case LFT_REQUEST_HEADER:
593
594 if (al->request)
595 sb = al->request->header.getByName(fmt->data.header.header);
596
597 out = sb.termedBuf();
598
599 quote = 1;
600
601 break;
602
603 case LFT_ADAPTED_REQUEST_HEADER:
604
605 if (al->adapted_request)
606 sb = al->adapted_request->header.getByName(fmt->data.header.header);
607
608 out = sb.termedBuf();
609
610 quote = 1;
611
612 break;
613
614 case LFT_REPLY_HEADER:
615 if (al->reply)
616 sb = al->reply->header.getByName(fmt->data.header.header);
617
618 out = sb.termedBuf();
619
620 quote = 1;
621
622 break;
623
624 #if USE_ADAPTATION
625 case LFT_ADAPTATION_SUM_XACT_TIMES:
626 if (al->request) {
627 Adaptation::History::Pointer ah = al->request->adaptHistory();
628 if (ah != NULL)
629 ah->sumLogString(fmt->data.string, sb);
630 out = sb.termedBuf();
631 }
632 break;
633
634 case LFT_ADAPTATION_ALL_XACT_TIMES:
635 if (al->request) {
636 Adaptation::History::Pointer ah = al->request->adaptHistory();
637 if (ah != NULL)
638 ah->allLogString(fmt->data.string, sb);
639 out = sb.termedBuf();
640 }
641 break;
642
643 case LFT_ADAPTATION_LAST_HEADER:
644 if (al->request) {
645 const Adaptation::History::Pointer ah = al->request->adaptHistory();
646 if (ah != NULL) // XXX: add adapt::<all_h but use lastMeta here
647 sb = ah->allMeta.getByName(fmt->data.header.header);
648 }
649
650 // XXX: here and elsewhere: move such code inside the if guard
651 out = sb.termedBuf();
652
653 quote = 1;
654
655 break;
656
657 case LFT_ADAPTATION_LAST_HEADER_ELEM:
658 if (al->request) {
659 const Adaptation::History::Pointer ah = al->request->adaptHistory();
660 if (ah != NULL) // XXX: add adapt::<all_h but use lastMeta here
661 sb = ah->allMeta.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator);
662 }
663
664 out = sb.termedBuf();
665
666 quote = 1;
667
668 break;
669
670 case LFT_ADAPTATION_LAST_ALL_HEADERS:
671 out = al->adapt.last_meta;
672
673 quote = 1;
674
675 break;
676 #endif
677
678 #if ICAP_CLIENT
679 case LFT_ICAP_ADDR:
680 if (!out)
681 out = al->icap.hostAddr.toStr(tmp,1024);
682 break;
683
684 case LFT_ICAP_SERV_NAME:
685 out = al->icap.serviceName.termedBuf();
686 break;
687
688 case LFT_ICAP_REQUEST_URI:
689 out = al->icap.reqUri.termedBuf();
690 break;
691
692 case LFT_ICAP_REQUEST_METHOD:
693 out = Adaptation::Icap::ICAP::methodStr(al->icap.reqMethod);
694 break;
695
696 case LFT_ICAP_BYTES_SENT:
697 outoff = al->icap.bytesSent;
698 dooff = 1;
699 break;
700
701 case LFT_ICAP_BYTES_READ:
702 outoff = al->icap.bytesRead;
703 dooff = 1;
704 break;
705
706 case LFT_ICAP_BODY_BYTES_READ:
707 if (al->icap.bodyBytesRead >= 0) {
708 outoff = al->icap.bodyBytesRead;
709 dooff = 1;
710 }
711 // else if icap.bodyBytesRead < 0, we do not have any http data,
712 // so just print a "-" (204 responses etc)
713 break;
714
715 case LFT_ICAP_REQ_HEADER:
716 if (NULL != al->icap.request) {
717 sb = al->icap.request->header.getByName(fmt->data.header.header);
718 out = sb.termedBuf();
719 quote = 1;
720 }
721 break;
722
723 case LFT_ICAP_REQ_HEADER_ELEM:
724 if (al->icap.request)
725 sb = al->icap.request->header.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator);
726
727 out = sb.termedBuf();
728
729 quote = 1;
730
731 break;
732
733 case LFT_ICAP_REQ_ALL_HEADERS:
734 if (al->icap.request) {
735 HttpHeaderPos pos = HttpHeaderInitPos;
736 while (const HttpHeaderEntry *e = al->icap.request->header.getEntry(&pos)) {
737 sb.append(e->name);
738 sb.append(": ");
739 sb.append(e->value);
740 sb.append("\r\n");
741 }
742 out = sb.termedBuf();
743 quote = 1;
744 }
745 break;
746
747 case LFT_ICAP_REP_HEADER:
748 if (NULL != al->icap.reply) {
749 sb = al->icap.reply->header.getByName(fmt->data.header.header);
750 out = sb.termedBuf();
751 quote = 1;
752 }
753 break;
754
755 case LFT_ICAP_REP_HEADER_ELEM:
756 if (NULL != al->icap.reply)
757 sb = al->icap.reply->header.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator);
758
759 out = sb.termedBuf();
760
761 quote = 1;
762
763 break;
764
765 case LFT_ICAP_REP_ALL_HEADERS:
766 if (al->icap.reply) {
767 HttpHeaderPos pos = HttpHeaderInitPos;
768 while (const HttpHeaderEntry *e = al->icap.reply->header.getEntry(&pos)) {
769 sb.append(e->name);
770 sb.append(": ");
771 sb.append(e->value);
772 sb.append("\r\n");
773 }
774 out = sb.termedBuf();
775 quote = 1;
776 }
777 break;
778
779 case LFT_ICAP_TR_RESPONSE_TIME:
780 outtv = al->icap.trTime;
781 doMsec = 1;
782 break;
783
784 case LFT_ICAP_IO_TIME:
785 outtv = al->icap.ioTime;
786 doMsec = 1;
787 break;
788
789 case LFT_ICAP_STATUS_CODE:
790 outint = al->icap.resStatus;
791 doint = 1;
792 break;
793
794 case LFT_ICAP_OUTCOME:
795 out = al->icap.outcome;
796 break;
797
798 case LFT_ICAP_TOTAL_TIME:
799 outtv = al->icap.processingTime;
800 doMsec = 1;
801 break;
802 #endif
803 case LFT_REQUEST_HEADER_ELEM:
804 if (al->request)
805 sb = al->request->header.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator);
806
807 out = sb.termedBuf();
808
809 quote = 1;
810
811 break;
812
813 case LFT_ADAPTED_REQUEST_HEADER_ELEM:
814 if (al->adapted_request)
815 sb = al->adapted_request->header.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator);
816
817 out = sb.termedBuf();
818
819 quote = 1;
820
821 break;
822
823 case LFT_REPLY_HEADER_ELEM:
824 if (al->reply)
825 sb = al->reply->header.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator);
826
827 out = sb.termedBuf();
828
829 quote = 1;
830
831 break;
832
833 case LFT_REQUEST_ALL_HEADERS:
834 out = al->headers.request;
835
836 quote = 1;
837
838 break;
839
840 case LFT_ADAPTED_REQUEST_ALL_HEADERS:
841 out = al->headers.adapted_request;
842
843 quote = 1;
844
845 break;
846
847 case LFT_REPLY_ALL_HEADERS:
848 out = al->headers.reply;
849
850 quote = 1;
851
852 break;
853
854 case LFT_USER_NAME:
855 #if USE_AUTH
856 if (al->request && al->request->auth_user_request != NULL)
857 out = strOrNull(al->request->auth_user_request->username());
858 #endif
859 if (!out && al->request && al->request->extacl_user.size()) {
860 if (const char *t = al->request->extacl_user.termedBuf())
861 out = t;
862 }
863
864 if (!out)
865 out = strOrNull(al->cache.extuser);
866
867 #if USE_OPENSSL
868 if (!out)
869 out = strOrNull(al->cache.ssluser);
870 #endif
871 if (!out)
872 out = strOrNull(al->cache.rfc931);
873 break;
874
875 case LFT_USER_LOGIN:
876 #if USE_AUTH
877 if (al->request && al->request->auth_user_request != NULL)
878 out = strOrNull(al->request->auth_user_request->username());
879 #endif
880 break;
881
882 case LFT_USER_IDENT:
883 out = strOrNull(al->cache.rfc931);
884 break;
885
886 case LFT_USER_EXTERNAL:
887 if (al->request && al->request->extacl_user.size()) {
888 if (const char *t = al->request->extacl_user.termedBuf())
889 out = t;
890 }
891
892 if (!out)
893 out = strOrNull(al->cache.extuser);
894 break;
895
896 /* case LFT_USER_REALM: */
897 /* case LFT_USER_SCHEME: */
898
899 // the fmt->type can not be LFT_HTTP_SENT_STATUS_CODE_OLD_30
900 // but compiler complains if ommited
901 case LFT_HTTP_SENT_STATUS_CODE_OLD_30:
902 case LFT_HTTP_SENT_STATUS_CODE:
903 outint = al->http.code;
904
905 doint = 1;
906
907 break;
908
909 case LFT_HTTP_RECEIVED_STATUS_CODE:
910 if (al->hier.peer_reply_status == Http::scNone) {
911 out = "-";
912 } else {
913 outint = al->hier.peer_reply_status;
914 doint = 1;
915 }
916 break;
917 /* case LFT_HTTP_STATUS:
918 * out = statusline->text;
919 * quote = 1;
920 * break;
921 */
922 case LFT_HTTP_BODY_BYTES_READ:
923 if (al->hier.bodyBytesRead >= 0) {
924 outoff = al->hier.bodyBytesRead;
925 dooff = 1;
926 }
927 // else if hier.bodyBytesRead < 0 we did not have any data exchange with
928 // a peer server so just print a "-" (eg requests served from cache,
929 // or internal error messages).
930 break;
931
932 case LFT_SQUID_STATUS:
933 out = al->cache.code.c_str();
934 break;
935
936 case LFT_SQUID_ERROR:
937 if (al->request && al->request->errType != ERR_NONE)
938 out = errorPageName(al->request->errType);
939 break;
940
941 case LFT_SQUID_ERROR_DETAIL:
942 #if USE_OPENSSL
943 if (al->request && al->request->errType == ERR_SECURE_CONNECT_FAIL) {
944 if (! (out = Ssl::GetErrorName(al->request->errDetail)))
945 out = sslErrorName(al->request->errDetail, tmp, sizeof(tmp));
946 } else
947 #endif
948 if (al->request && al->request->errDetail != ERR_DETAIL_NONE) {
949 if (al->request->errDetail > ERR_DETAIL_START && al->request->errDetail < ERR_DETAIL_MAX)
950 out = errorDetailName(al->request->errDetail);
951 else {
952 if (al->request->errDetail >= ERR_DETAIL_EXCEPTION_START)
953 snprintf(tmp, sizeof(tmp), "%s=0x%X",
954 errorDetailName(al->request->errDetail), (uint32_t) al->request->errDetail);
955 else
956 snprintf(tmp, sizeof(tmp), "%s=%d",
957 errorDetailName(al->request->errDetail), al->request->errDetail);
958 out = tmp;
959 }
960 }
961 break;
962
963 case LFT_SQUID_HIERARCHY:
964 if (al->hier.ping.timedout)
965 mb.append("TIMEOUT_", 8);
966
967 out = hier_code_str[al->hier.code];
968
969 break;
970
971 case LFT_MIME_TYPE:
972 out = al->http.content_type;
973
974 break;
975
976 case LFT_CLIENT_REQ_METHOD:
977 if (al->request) {
978 const SBuf &s = al->request->method.image();
979 sb.append(s.rawContent(), s.length());
980 out = sb.termedBuf();
981 quote = 1;
982 }
983 break;
984
985 case LFT_CLIENT_REQ_URI:
986 // original client URI
987 if (al->request) {
988 const SBuf &s = al->request->effectiveRequestUri();
989 sb.append(s.rawContent(), s.length());
990 out = sb.termedBuf();
991 quote = 1;
992 }
993 break;
994
995 case LFT_CLIENT_REQ_URLSCHEME:
996 if (al->request) {
997 out = al->request->url.getScheme().c_str();
998 quote = 1;
999 }
1000 break;
1001
1002 case LFT_CLIENT_REQ_URLDOMAIN:
1003 if (al->request) {
1004 out = al->request->url.host();
1005 quote = 1;
1006 }
1007 break;
1008
1009 case LFT_CLIENT_REQ_URLPORT:
1010 if (al->request) {
1011 outint = al->request->url.port();
1012 doint = 1;
1013 }
1014 break;
1015
1016 case LFT_REQUEST_URLPATH_OLD_31:
1017 case LFT_CLIENT_REQ_URLPATH:
1018 if (al->request) {
1019 SBuf s = al->request->url.path();
1020 out = s.c_str();
1021 quote = 1;
1022 }
1023 break;
1024
1025 case LFT_CLIENT_REQ_VERSION:
1026 if (al->request) {
1027 snprintf(tmp, sizeof(tmp), "%d.%d", (int) al->request->http_ver.major, (int) al->request->http_ver.minor);
1028 out = tmp;
1029 }
1030 break;
1031
1032 case LFT_REQUEST_METHOD:
1033 {
1034 const SBuf s(al->getLogMethod());
1035 sb.append(s.rawContent(), s.length());
1036 out = sb.termedBuf();
1037 quote = 1;
1038 }
1039 break;
1040
1041 case LFT_REQUEST_URI:
1042 out = al->url;
1043 break;
1044
1045 case LFT_REQUEST_VERSION_OLD_2X:
1046 case LFT_REQUEST_VERSION:
1047 snprintf(tmp, sizeof(tmp), "%d.%d", (int) al->http.version.major, (int) al->http.version.minor);
1048 out = tmp;
1049 break;
1050
1051 case LFT_SERVER_REQ_METHOD:
1052 if (al->adapted_request) {
1053 const SBuf &s = al->adapted_request->method.image();
1054 sb.append(s.rawContent(), s.length());
1055 out = sb.termedBuf();
1056 quote = 1;
1057 }
1058 break;
1059
1060 case LFT_SERVER_REQ_URI:
1061 // adapted request URI sent to server/peer
1062 if (al->adapted_request) {
1063 const SBuf &s = al->adapted_request->effectiveRequestUri();
1064 sb.append(s.rawContent(), s.length());
1065 out = sb.termedBuf();
1066 quote = 1;
1067 }
1068 break;
1069
1070 case LFT_SERVER_REQ_URLSCHEME:
1071 if (al->adapted_request) {
1072 out = al->adapted_request->url.getScheme().c_str();
1073 quote = 1;
1074 }
1075 break;
1076
1077 case LFT_SERVER_REQ_URLDOMAIN:
1078 if (al->adapted_request) {
1079 out = al->adapted_request->url.host();
1080 quote = 1;
1081 }
1082 break;
1083
1084 case LFT_SERVER_REQ_URLPORT:
1085 if (al->adapted_request) {
1086 outint = al->adapted_request->url.port();
1087 doint = 1;
1088 }
1089 break;
1090
1091 case LFT_SERVER_REQ_URLPATH:
1092 if (al->adapted_request) {
1093 SBuf s = al->adapted_request->url.path();
1094 out = s.c_str();
1095 quote = 1;
1096 }
1097 break;
1098
1099 case LFT_SERVER_REQ_VERSION:
1100 if (al->adapted_request) {
1101 snprintf(tmp, sizeof(tmp), "%d.%d",
1102 (int) al->adapted_request->http_ver.major,
1103 (int) al->adapted_request->http_ver.minor);
1104 out = tmp;
1105 }
1106 break;
1107
1108 case LFT_CLIENT_REQUEST_SIZE_TOTAL:
1109 outoff = al->http.clientRequestSz.messageTotal();
1110 dooff = 1;
1111 break;
1112
1113 case LFT_CLIENT_REQUEST_SIZE_HEADERS:
1114 outoff = al->http.clientRequestSz.header;
1115 dooff =1;
1116 break;
1117
1118 /*case LFT_REQUEST_SIZE_BODY: */
1119 /*case LFT_REQUEST_SIZE_BODY_NO_TE: */
1120
1121 case LFT_ADAPTED_REPLY_SIZE_TOTAL:
1122 outoff = al->http.clientReplySz.messageTotal();
1123 dooff = 1;
1124 break;
1125
1126 case LFT_REPLY_HIGHOFFSET:
1127 outoff = al->cache.highOffset;
1128
1129 dooff = 1;
1130
1131 break;
1132
1133 case LFT_REPLY_OBJECTSIZE:
1134 outoff = al->cache.objectSize;
1135
1136 dooff = 1;
1137
1138 break;
1139
1140 case LFT_ADAPTED_REPLY_SIZE_HEADERS:
1141 outint = al->http.clientReplySz.header;
1142 doint = 1;
1143 break;
1144
1145 /*case LFT_REPLY_SIZE_BODY: */
1146 /*case LFT_REPLY_SIZE_BODY_NO_TE: */
1147
1148 case LFT_CLIENT_IO_SIZE_TOTAL:
1149 outint = al->http.clientRequestSz.messageTotal() + al->http.clientReplySz.messageTotal();
1150 doint = 1;
1151 break;
1152 /*case LFT_SERVER_IO_SIZE_TOTAL: */
1153
1154 case LFT_TAG:
1155 if (al->request)
1156 out = al->request->tag.termedBuf();
1157
1158 quote = 1;
1159
1160 break;
1161
1162 case LFT_EXT_LOG:
1163 if (al->request)
1164 out = al->request->extacl_log.termedBuf();
1165
1166 quote = 1;
1167
1168 break;
1169
1170 case LFT_SEQUENCE_NUMBER:
1171 outoff = logSequenceNumber;
1172 dooff = 1;
1173 break;
1174
1175 #if USE_OPENSSL
1176 case LFT_SSL_BUMP_MODE: {
1177 const Ssl::BumpMode mode = static_cast<Ssl::BumpMode>(al->ssl.bumpMode);
1178 // for Ssl::bumpEnd, Ssl::bumpMode() returns NULL and we log '-'
1179 out = Ssl::bumpMode(mode);
1180 }
1181 break;
1182
1183 case LFT_EXT_ACL_USER_CERT_RAW:
1184 if (al->request) {
1185 ConnStateData *conn = al->request->clientConnectionManager.get();
1186 if (conn && Comm::IsConnOpen(conn->clientConnection)) {
1187 if (auto ssl = fd_table[conn->clientConnection->fd].ssl.get())
1188 out = sslGetUserCertificatePEM(ssl);
1189 }
1190 }
1191 break;
1192
1193 case LFT_EXT_ACL_USER_CERTCHAIN_RAW:
1194 if (al->request) {
1195 ConnStateData *conn = al->request->clientConnectionManager.get();
1196 if (conn && Comm::IsConnOpen(conn->clientConnection)) {
1197 if (auto ssl = fd_table[conn->clientConnection->fd].ssl.get())
1198 out = sslGetUserCertificatePEM(ssl);
1199 }
1200 }
1201 break;
1202
1203 case LFT_EXT_ACL_USER_CERT:
1204 if (al->request) {
1205 ConnStateData *conn = al->request->clientConnectionManager.get();
1206 if (conn && Comm::IsConnOpen(conn->clientConnection)) {
1207 if (auto ssl = fd_table[conn->clientConnection->fd].ssl.get())
1208 out = sslGetUserAttribute(ssl, format->data.header.header);
1209 }
1210 }
1211 break;
1212
1213 case LFT_EXT_ACL_USER_CA_CERT:
1214 if (al->request) {
1215 ConnStateData *conn = al->request->clientConnectionManager.get();
1216 if (conn && Comm::IsConnOpen(conn->clientConnection)) {
1217 if (auto ssl = fd_table[conn->clientConnection->fd].ssl.get())
1218 out = sslGetCAAttribute(ssl, format->data.header.header);
1219 }
1220 }
1221 break;
1222
1223 case LFT_SSL_USER_CERT_SUBJECT:
1224 if (X509 *cert = al->cache.sslClientCert.get()) {
1225 if (X509_NAME *subject = X509_get_subject_name(cert)) {
1226 X509_NAME_oneline(subject, tmp, sizeof(tmp));
1227 out = tmp;
1228 }
1229 }
1230 break;
1231
1232 case LFT_SSL_USER_CERT_ISSUER:
1233 if (X509 *cert = al->cache.sslClientCert.get()) {
1234 if (X509_NAME *issuer = X509_get_issuer_name(cert)) {
1235 X509_NAME_oneline(issuer, tmp, sizeof(tmp));
1236 out = tmp;
1237 }
1238 }
1239 break;
1240
1241 case LFT_SSL_CLIENT_SNI:
1242 if (al->request && al->request->clientConnectionManager.valid()) {
1243 if (Ssl::ServerBump * srvBump = al->request->clientConnectionManager->serverBump()) {
1244 if (!srvBump->clientSni.isEmpty())
1245 out = srvBump->clientSni.c_str();
1246 }
1247 }
1248 break;
1249
1250 case LFT_SSL_SERVER_CERT_ERRORS:
1251 if (al->request && al->request->clientConnectionManager.valid()) {
1252 if (Ssl::ServerBump * srvBump = al->request->clientConnectionManager->serverBump()) {
1253 const char *separator = fmt->data.string ? fmt->data.string : ":";
1254 for (Ssl::CertErrors const *sslError = srvBump->sslErrors(); sslError != NULL; sslError = sslError->next) {
1255 if (sb.size())
1256 sb.append(separator);
1257 if (const char *errorName = Ssl::GetErrorName(sslError->element.code))
1258 sb.append(errorName);
1259 else
1260 sb.append(sslErrorName(sslError->element.code, tmp, sizeof(tmp)));
1261 if (sslError->element.depth >= 0) {
1262 snprintf(tmp, sizeof(tmp), "@depth=%d", sslError->element.depth);
1263 sb.append(tmp);
1264 }
1265 }
1266 if (sb.size())
1267 out = sb.termedBuf();
1268 }
1269 }
1270 break;
1271
1272 case LFT_SSL_SERVER_CERT_ISSUER:
1273 case LFT_SSL_SERVER_CERT_SUBJECT:
1274 if (al->request && al->request->clientConnectionManager.valid()) {
1275 if (Ssl::ServerBump * srvBump = al->request->clientConnectionManager->serverBump()) {
1276 if (X509 *serverCert = srvBump->serverCert.get()) {
1277 if (fmt->type == LFT_SSL_SERVER_CERT_SUBJECT)
1278 out = Ssl::GetX509UserAttribute(serverCert, "DN");
1279 else
1280 out = Ssl::GetX509CAAttribute(serverCert, "DN");
1281 }
1282 }
1283 }
1284 break;
1285
1286 case LFT_TLS_CLIENT_NEGOTIATED_VERSION:
1287 if (al->tcpClient != nullptr && al->tcpClient->hasTlsNegotiations())
1288 out = al->tcpClient->hasTlsNegotiations()->negotiatedVersion();
1289 break;
1290
1291 case LFT_TLS_SERVER_NEGOTIATED_VERSION:
1292 if (al->hier.tcpServer != nullptr && al->hier.tcpServer->hasTlsNegotiations())
1293 out = al->hier.tcpServer->hasTlsNegotiations()->negotiatedVersion();
1294 break;
1295
1296 case LFT_TLS_CLIENT_RECEIVED_HELLO_VERSION:
1297 if (al->tcpClient != nullptr && al->tcpClient->hasTlsNegotiations())
1298 out = al->tcpClient->hasTlsNegotiations()->helloVersion();
1299 break;
1300
1301 case LFT_TLS_SERVER_RECEIVED_HELLO_VERSION:
1302 if (al->hier.tcpServer != nullptr && al->hier.tcpServer->hasTlsNegotiations())
1303 out = al->hier.tcpServer->hasTlsNegotiations()->helloVersion();
1304 break;
1305
1306 case LFT_TLS_CLIENT_SUPPORTED_VERSION:
1307 if (al->tcpClient != nullptr && al->tcpClient->hasTlsNegotiations())
1308 out = al->tcpClient->hasTlsNegotiations()->supportedVersion();
1309 break;
1310
1311 case LFT_TLS_SERVER_SUPPORTED_VERSION:
1312 if (al->hier.tcpServer != nullptr && al->hier.tcpServer->hasTlsNegotiations())
1313 out = al->hier.tcpServer->hasTlsNegotiations()->supportedVersion();
1314 break;
1315
1316 case LFT_TLS_CLIENT_NEGOTIATED_CIPHER:
1317 if (al->tcpClient != nullptr && al->tcpClient->hasTlsNegotiations())
1318 out = al->tcpClient->hasTlsNegotiations()->cipherName();
1319 break;
1320
1321 case LFT_TLS_SERVER_NEGOTIATED_CIPHER:
1322 if (al->hier.tcpServer != nullptr && al->hier.tcpServer->hasTlsNegotiations())
1323 out = al->hier.tcpServer->hasTlsNegotiations()->cipherName();
1324 break;
1325 #endif
1326
1327 case LFT_REQUEST_URLGROUP_OLD_2X:
1328 assert(LFT_REQUEST_URLGROUP_OLD_2X == 0); // should never happen.
1329
1330 case LFT_NOTE:
1331 tmp[0] = fmt->data.header.separator;
1332 tmp[1] = '\0';
1333 if (fmt->data.header.header && *fmt->data.header.header) {
1334 const char *separator = tmp;
1335 #if USE_ADAPTATION
1336 Adaptation::History::Pointer ah = al->request ? al->request->adaptHistory() : Adaptation::History::Pointer();
1337 if (ah != NULL && ah->metaHeaders != NULL) {
1338 if (const char *meta = ah->metaHeaders->find(fmt->data.header.header, separator))
1339 sb.append(meta);
1340 }
1341 #endif
1342 if (al->notes != NULL) {
1343 if (const char *note = al->notes->find(fmt->data.header.header, separator)) {
1344 if (sb.size())
1345 sb.append(separator);
1346 sb.append(note);
1347 }
1348 }
1349 out = sb.termedBuf();
1350 quote = 1;
1351 } else {
1352 // if no argument given use default "\r\n" as notes separator
1353 const char *separator = fmt->data.string ? tmp : "\r\n";
1354 #if USE_ADAPTATION
1355 Adaptation::History::Pointer ah = al->request ? al->request->adaptHistory() : Adaptation::History::Pointer();
1356 if (ah != NULL && ah->metaHeaders != NULL && !ah->metaHeaders->empty())
1357 sb.append(ah->metaHeaders->toString(separator));
1358 #endif
1359 if (al->notes != NULL && !al->notes->empty())
1360 sb.append(al->notes->toString(separator));
1361
1362 out = sb.termedBuf();
1363 quote = 1;
1364 }
1365 break;
1366
1367 case LFT_CREDENTIALS:
1368 #if USE_AUTH
1369 if (al->request && al->request->auth_user_request != NULL)
1370 out = strOrNull(al->request->auth_user_request->credentialsStr());
1371 #endif
1372
1373 break;
1374
1375 case LFT_PERCENT:
1376 out = "%";
1377 break;
1378
1379 case LFT_EXT_ACL_NAME:
1380 out = al->lastAclName;
1381 break;
1382
1383 case LFT_EXT_ACL_DATA:
1384 if (!al->lastAclData.isEmpty())
1385 out = al->lastAclData.c_str();
1386 break;
1387 }
1388
1389 if (dooff) {
1390 snprintf(tmp, sizeof(tmp), "%0*" PRId64, fmt->zero && fmt->widthMin >= 0 ? fmt->widthMin : 0, outoff);
1391 out = tmp;
1392
1393 } else if (doint) {
1394 snprintf(tmp, sizeof(tmp), "%0*ld", fmt->zero && fmt->widthMin >= 0 ? fmt->widthMin : 0, outint);
1395 out = tmp;
1396 } else if (doMsec) {
1397 if (fmt->widthMax < 0) {
1398 snprintf(tmp, sizeof(tmp), "%0*ld", fmt->widthMin , tvToMsec(outtv));
1399 } else {
1400 int precision = fmt->widthMax;
1401 snprintf(tmp, sizeof(tmp), "%0*" PRId64 ".%0*" PRId64 "", fmt->zero && (fmt->widthMin - precision - 1 >= 0) ? fmt->widthMin - precision - 1 : 0, static_cast<int64_t>(outtv.tv_sec * 1000 + outtv.tv_usec / 1000), precision, static_cast<int64_t>((outtv.tv_usec % 1000 )* (1000 / fmt->divisor)));
1402 }
1403 out = tmp;
1404 } else if (doSec) {
1405 int precision = fmt->widthMax >=0 ? fmt->widthMax :3;
1406 snprintf(tmp, sizeof(tmp), "%0*" PRId64 ".%0*d", fmt->zero && (fmt->widthMin - precision - 1 >= 0) ? fmt->widthMin - precision - 1 : 0, static_cast<int64_t>(outtv.tv_sec), precision, (int)(outtv.tv_usec / fmt->divisor));
1407 out = tmp;
1408 }
1409
1410 if (out && *out) {
1411 if (quote || fmt->quote != LOG_QUOTE_NONE) {
1412 char *newout = NULL;
1413 int newfree = 0;
1414
1415 switch (fmt->quote) {
1416
1417 case LOG_QUOTE_NONE:
1418 newout = rfc1738_escape_unescaped(out);
1419 break;
1420
1421 case LOG_QUOTE_QUOTES: {
1422 size_t out_len = static_cast<size_t>(strlen(out)) * 2 + 1;
1423 if (out_len >= sizeof(tmp)) {
1424 newout = (char *)xmalloc(out_len);
1425 newfree = 1;
1426 } else
1427 newout = tmp;
1428 log_quoted_string(out, newout);
1429 }
1430 break;
1431
1432 case LOG_QUOTE_MIMEBLOB:
1433 newout = QuoteMimeBlob(out);
1434 newfree = 1;
1435 break;
1436
1437 case LOG_QUOTE_URL:
1438 newout = rfc1738_escape(out);
1439 break;
1440
1441 case LOG_QUOTE_SHELL: {
1442 MemBuf mbq;
1443 mbq.init();
1444 strwordquote(&mbq, out);
1445 newout = mbq.content();
1446 mbq.stolen = 1;
1447 newfree = 1;
1448 }
1449 break;
1450
1451 case LOG_QUOTE_RAW:
1452 break;
1453 }
1454
1455 if (newout) {
1456 if (dofree)
1457 safe_free(out);
1458
1459 out = newout;
1460
1461 dofree = newfree;
1462 }
1463 }
1464
1465 // enforce width limits if configured
1466 const bool haveMaxWidth = fmt->widthMax >=0 && !doint && !dooff && !doMsec && !doSec;
1467 if (haveMaxWidth || fmt->widthMin) {
1468 const int minWidth = fmt->widthMin >= 0 ?
1469 fmt->widthMin :0;
1470 const int maxWidth = haveMaxWidth ?
1471 fmt->widthMax : strlen(out);
1472
1473 if (fmt->left)
1474 mb.appendf("%-*.*s", minWidth, maxWidth, out);
1475 else
1476 mb.appendf("%*.*s", minWidth, maxWidth, out);
1477 } else
1478 mb.append(out, strlen(out));
1479 } else {
1480 mb.append("-", 1);
1481 }
1482
1483 if (fmt->space)
1484 mb.append(" ", 1);
1485
1486 sb.clean();
1487
1488 if (dofree)
1489 safe_free(out);
1490 }
1491 }
1492