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