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