1 /** @file
2
3 A brief file description
4
5 @section license License
6
7 Licensed to the Apache Software Foundation (ASF) under one
8 or more contributor license agreements. See the NOTICE file
9 distributed with this work for additional information
10 regarding copyright ownership. The ASF licenses this file
11 to you under the Apache License, Version 2.0 (the
12 "License"); you may not use this file except in compliance
13 with the License. You may obtain a copy of the License at
14
15 http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22 */
23
24 #include <bitset>
25 #include <algorithm>
26 #include <array>
27 #include <string_view>
28
29 #include "tscore/ink_platform.h"
30 #include "tscore/BufferWriter.h"
31
32 #include "HttpTransact.h"
33 #include "HttpTransactHeaders.h"
34 #include "HTTP.h"
35 #include "HdrUtils.h"
36 #include "HttpCompat.h"
37 #include "HttpSM.h"
38 #include "PoolableSession.h"
39
40 #include "I_Machine.h"
41
42 using namespace std::literals;
43
44 bool
is_method_cacheable(const HttpConfigParams * http_config_param,const int method)45 HttpTransactHeaders::is_method_cacheable(const HttpConfigParams *http_config_param, const int method)
46 {
47 return (method == HTTP_WKSIDX_GET || method == HTTP_WKSIDX_HEAD ||
48 (http_config_param->cache_post_method == 1 && method == HTTP_WKSIDX_POST));
49 }
50
51 bool
is_method_cache_lookupable(int method)52 HttpTransactHeaders::is_method_cache_lookupable(int method)
53 {
54 // responses to GET, HEAD, and POST are cacheable
55 // URL's requested in DELETE and PUT are looked up to remove cached copies
56 return (method == HTTP_WKSIDX_GET || method == HTTP_WKSIDX_HEAD || method == HTTP_WKSIDX_POST || method == HTTP_WKSIDX_DELETE ||
57 method == HTTP_WKSIDX_PUT || method == HTTP_WKSIDX_PURGE || method == HTTP_WKSIDX_PUSH);
58 }
59
60 bool
is_this_a_hop_by_hop_header(const char * field_name)61 HttpTransactHeaders::is_this_a_hop_by_hop_header(const char *field_name)
62 {
63 if (!hdrtoken_is_wks(field_name)) {
64 return (false);
65 }
66 if ((hdrtoken_wks_to_flags(field_name) & HTIF_HOPBYHOP) && (field_name != MIME_FIELD_KEEP_ALIVE)) {
67 return (true);
68 } else {
69 return (false);
70 }
71 }
72
73 bool
is_this_method_supported(int the_scheme,int the_method)74 HttpTransactHeaders::is_this_method_supported(int the_scheme, int the_method)
75 {
76 if (the_method == HTTP_WKSIDX_CONNECT) {
77 return true;
78 } else if (the_scheme == URL_WKSIDX_HTTP || the_scheme == URL_WKSIDX_HTTPS) {
79 return is_this_http_method_supported(the_method);
80 } else if ((the_scheme == URL_WKSIDX_WS || the_scheme == URL_WKSIDX_WSS) && the_method == HTTP_WKSIDX_GET) {
81 return true;
82 } else {
83 return false;
84 }
85 }
86
87 bool
is_method_safe(int method)88 HttpTransactHeaders::is_method_safe(int method)
89 {
90 return (method == HTTP_WKSIDX_GET || method == HTTP_WKSIDX_OPTIONS || method == HTTP_WKSIDX_HEAD || method == HTTP_WKSIDX_TRACE);
91 }
92
93 bool
is_method_idempotent(int method)94 HttpTransactHeaders::is_method_idempotent(int method)
95 {
96 return (method == HTTP_WKSIDX_CONNECT || method == HTTP_WKSIDX_DELETE || method == HTTP_WKSIDX_GET ||
97 method == HTTP_WKSIDX_HEAD || method == HTTP_WKSIDX_PUT || method == HTTP_WKSIDX_OPTIONS || method == HTTP_WKSIDX_TRACE);
98 }
99
100 void
insert_supported_methods_in_response(HTTPHdr * response,int scheme)101 HttpTransactHeaders::insert_supported_methods_in_response(HTTPHdr *response, int scheme)
102 {
103 int method_output_lengths[32];
104 const char *methods[] = {
105 HTTP_METHOD_CONNECT, HTTP_METHOD_DELETE, HTTP_METHOD_GET, HTTP_METHOD_HEAD, HTTP_METHOD_OPTIONS,
106 HTTP_METHOD_POST, HTTP_METHOD_PURGE, HTTP_METHOD_PUT, HTTP_METHOD_PUSH, HTTP_METHOD_TRACE,
107 };
108 char inline_buffer[64];
109 char *alloced_buffer, *value_buffer;
110
111 int nmethods = sizeof(methods) / sizeof(methods[0]);
112 ink_assert(nmethods <= 32);
113
114 char *p;
115 int i, is_supported;
116 size_t bytes = 0;
117 int num_methods_supported = 0;
118 MIMEField *field;
119
120 // step 1: determine supported methods, count bytes & allocate
121 for (i = 0; i < nmethods; i++) {
122 const char *method_wks = methods[i];
123 ink_assert(hdrtoken_is_wks(method_wks));
124
125 is_supported = is_this_method_supported(scheme, hdrtoken_wks_to_index(method_wks));
126
127 if (is_supported) {
128 ++num_methods_supported;
129 method_output_lengths[i] = hdrtoken_wks_to_length(method_wks);
130 bytes += method_output_lengths[i];
131 if (num_methods_supported > 1) {
132 bytes += 2; // +2 if need leading ", "
133 }
134 } else {
135 method_output_lengths[i] = 0;
136 }
137 }
138
139 // step 2: create Allow field if not present
140 field = response->field_find(MIME_FIELD_ALLOW, MIME_LEN_ALLOW);
141 if (!field) {
142 field = response->field_create(MIME_FIELD_ALLOW, MIME_LEN_ALLOW);
143 response->field_attach(field);
144 }
145 // step 3: get a big enough buffer
146 if (bytes <= sizeof(inline_buffer)) {
147 alloced_buffer = nullptr;
148 value_buffer = inline_buffer;
149 } else {
150 alloced_buffer = static_cast<char *>(ats_malloc(bytes));
151 value_buffer = alloced_buffer;
152 }
153
154 // step 4: build the value
155 p = value_buffer;
156 for (i = 0; i < nmethods; i++) {
157 if (method_output_lengths[i]) {
158 memcpy(p, methods[i], method_output_lengths[i]);
159 p += method_output_lengths[i];
160 if (num_methods_supported > 1) {
161 *p++ = ',';
162 *p++ = ' ';
163 }
164 --num_methods_supported;
165 }
166 }
167
168 // FIXME: do we really want to append to old list, or replace old list?
169
170 // step 5: attach new allow list to end of previous list
171 field->value_append(response->m_heap, response->m_mime, value_buffer, bytes);
172
173 // step 6: free up temp storage
174 ats_free(alloced_buffer);
175 }
176
177 void
build_base_response(HTTPHdr * outgoing_response,HTTPStatus status,const char * reason_phrase,int reason_phrase_len,ink_time_t date)178 HttpTransactHeaders::build_base_response(HTTPHdr *outgoing_response, HTTPStatus status, const char *reason_phrase,
179 int reason_phrase_len, ink_time_t date)
180 {
181 if (!outgoing_response->valid()) {
182 outgoing_response->create(HTTP_TYPE_RESPONSE);
183 }
184
185 ink_assert(outgoing_response->type_get() == HTTP_TYPE_RESPONSE);
186
187 outgoing_response->version_set(HTTPVersion(1, 1));
188 outgoing_response->status_set(status);
189 outgoing_response->reason_set(reason_phrase, reason_phrase_len);
190 outgoing_response->set_date(date);
191 }
192
193 ////////////////////////////////////////////////////////////////////////
194 // Copy all non hop-by-hop header fields from src_hdr to new_hdr.
195 // If header Date: is not present or invalid in src_hdr,
196 // then the given date will be used.
197 void
copy_header_fields(HTTPHdr * src_hdr,HTTPHdr * new_hdr,bool retain_proxy_auth_hdrs,ink_time_t date)198 HttpTransactHeaders::copy_header_fields(HTTPHdr *src_hdr, HTTPHdr *new_hdr, bool retain_proxy_auth_hdrs, ink_time_t date)
199 {
200 ink_assert(src_hdr->valid());
201 ink_assert(!new_hdr->valid());
202
203 MIMEField *field;
204 MIMEFieldIter field_iter;
205 bool date_hdr = false;
206
207 // Start with an exact duplicate
208 new_hdr->copy(src_hdr);
209
210 // Nuke hop-by-hop headers
211 //
212 // The hop-by-hop header fields are laid out by the spec
213 // with two adjustments
214 // 1) we treat TE as hop-by-hop because spec implies
215 // that it is by declaring anyone who sends a TE must
216 // include TE in the connection header. This in
217 // my opinion error prone and if the client doesn't follow the spec
218 // we'll have problems with the TE being forwarded to the server
219 // and us caching the transfer encoded documents and then
220 // serving it to a client that can not handle it
221 // 2) Transfer encoding is copied. If the transfer encoding
222 // is changed for example by dechunking, the transfer encoding
223 // should be modified when when the decision is made to dechunk it
224
225 for (field = new_hdr->iter_get_first(&field_iter); field != nullptr; field = new_hdr->iter_get_next(&field_iter)) {
226 if (field->m_wks_idx == -1) {
227 continue;
228 }
229
230 int field_flags = hdrtoken_index_to_flags(field->m_wks_idx);
231
232 if (field_flags & HTIF_HOPBYHOP) {
233 // Delete header if not in special proxy_auth retention mode
234 if ((!retain_proxy_auth_hdrs) || (!(field_flags & HTIF_PROXYAUTH))) {
235 new_hdr->field_delete(field);
236 }
237 } else if (field->m_wks_idx == MIME_WKSIDX_DATE) {
238 date_hdr = true;
239 }
240 }
241
242 // Set date hdr if not already set and valid value passed in
243 if ((date_hdr == false) && (date > 0)) {
244 new_hdr->set_date(date);
245 }
246 }
247
248 ////////////////////////////////////////////////////////////////////////
249 // Just convert the outgoing request to the appropriate version
250 void
convert_request(HTTPVersion outgoing_ver,HTTPHdr * outgoing_request)251 HttpTransactHeaders::convert_request(HTTPVersion outgoing_ver, HTTPHdr *outgoing_request)
252 {
253 if (outgoing_ver == HTTPVersion(1, 0)) {
254 convert_to_1_0_request_header(outgoing_request);
255 } else if (outgoing_ver == HTTPVersion(1, 1)) {
256 convert_to_1_1_request_header(outgoing_request);
257 } else {
258 Debug("http_trans", "[HttpTransactHeaders::convert_request]"
259 "Unsupported Version - passing through");
260 }
261 }
262
263 ////////////////////////////////////////////////////////////////////////
264 // Just convert the outgoing response to the appropriate version
265 void
convert_response(HTTPVersion outgoing_ver,HTTPHdr * outgoing_response)266 HttpTransactHeaders::convert_response(HTTPVersion outgoing_ver, HTTPHdr *outgoing_response)
267 {
268 if (outgoing_ver == HTTPVersion(1, 0)) {
269 convert_to_1_0_response_header(outgoing_response);
270 } else if (outgoing_ver == HTTPVersion(1, 1)) {
271 convert_to_1_1_response_header(outgoing_response);
272 } else {
273 Debug("http_trans", "[HttpTransactHeaders::convert_response]"
274 "Unsupported Version - passing through");
275 }
276 }
277
278 ////////////////////////////////////////////////////////////////////////
279 // Take an existing outgoing request header and make it HTTP/1.0
280 void
convert_to_1_0_request_header(HTTPHdr * outgoing_request)281 HttpTransactHeaders::convert_to_1_0_request_header(HTTPHdr *outgoing_request)
282 {
283 // These are required
284 ink_assert(outgoing_request->url_get()->valid());
285
286 // Set HTTP version to 1.0
287 outgoing_request->version_set(HTTPVersion(1, 0));
288
289 // FIXME (P2): Need to change cache directives into pragma, cleanly
290 // Now, any Cache-Control hdr becomes Pragma: no-cache
291
292 if (outgoing_request->presence(MIME_PRESENCE_CACHE_CONTROL) && !outgoing_request->is_pragma_no_cache_set()) {
293 outgoing_request->value_append(MIME_FIELD_PRAGMA, MIME_LEN_PRAGMA, "no-cache", 8, true);
294 }
295 // We do not currently support chunked transfer encoding,
296 // so specify that response should use identity transfer coding.
297 // outgoing_request->value_insert(MIME_FIELD_TE, "identity;q=1.0");
298 // outgoing_request->value_insert(MIME_FIELD_TE, "chunked;q=0.0");
299 }
300
301 ////////////////////////////////////////////////////////////////////////
302 // Take an existing outgoing request header and make it HTTP/1.1
303 void
convert_to_1_1_request_header(HTTPHdr * outgoing_request)304 HttpTransactHeaders::convert_to_1_1_request_header(HTTPHdr *outgoing_request)
305 {
306 // These are required
307 ink_assert(outgoing_request->url_get()->valid());
308 ink_assert(outgoing_request->version_get() == HTTPVersion(1, 1));
309
310 if (outgoing_request->get_cooked_pragma_no_cache() && !(outgoing_request->get_cooked_cc_mask() & MIME_COOKED_MASK_CC_NO_CACHE)) {
311 outgoing_request->value_append(MIME_FIELD_CACHE_CONTROL, MIME_LEN_CACHE_CONTROL, "no-cache", 8, true);
312 }
313 // We do not currently support chunked transfer encoding,
314 // so specify that response should use identity transfer coding.
315 // outgoing_request->value_insert(MIME_FIELD_TE, "identity;q=1.0");
316 // outgoing_request->value_insert(MIME_FIELD_TE, "chunked;q=0.0");
317 }
318
319 ////////////////////////////////////////////////////////////////////////
320 // Take an existing outgoing response header and make it HTTP/1.0
321 void
convert_to_1_0_response_header(HTTPHdr * outgoing_response)322 HttpTransactHeaders::convert_to_1_0_response_header(HTTPHdr *outgoing_response)
323 {
324 // // These are required
325 // ink_assert(outgoing_response->status_get());
326 // ink_assert(outgoing_response->reason_get());
327
328 // Set HTTP version to 1.0
329 outgoing_response->version_set(HTTPVersion(1, 0));
330
331 // Keep-Alive?
332
333 // Cache-Control?
334 }
335
336 ////////////////////////////////////////////////////////////////////////
337 // Take an existing outgoing response header and make it HTTP/1.1
338 void
convert_to_1_1_response_header(HTTPHdr * outgoing_response)339 HttpTransactHeaders::convert_to_1_1_response_header(HTTPHdr *outgoing_response)
340 {
341 // These are required
342 ink_assert(outgoing_response->status_get());
343
344 // Set HTTP version to 1.1
345 // ink_assert(outgoing_response->version_get() == HTTPVersion (1, 1));
346 outgoing_response->version_set(HTTPVersion(1, 1));
347 }
348
349 ///////////////////////////////////////////////////////////////////////////////
350 // Name : calculate_document_age()
351 // Description: returns age of document
352 //
353 // Input :
354 // Output : ink_time_t age
355 //
356 // Details :
357 // Algorithm is straight out of March 1998 1.1 specs, Section 13.2.3
358 //
359 ///////////////////////////////////////////////////////////////////////////////
360 ink_time_t
calculate_document_age(ink_time_t request_time,ink_time_t response_time,HTTPHdr * base_response,ink_time_t base_response_date,ink_time_t now)361 HttpTransactHeaders::calculate_document_age(ink_time_t request_time, ink_time_t response_time, HTTPHdr *base_response,
362 ink_time_t base_response_date, ink_time_t now)
363 {
364 ink_time_t age_value = base_response->get_age();
365 ink_time_t date_value = 0;
366 ink_time_t apparent_age = 0;
367 ink_time_t corrected_received_age = 0;
368 ink_time_t response_delay = 0;
369 ink_time_t corrected_initial_age = 0;
370 ink_time_t current_age = 0;
371 ink_time_t resident_time = 0;
372 ink_time_t now_value = 0;
373
374 ink_time_t tmp_value = 0;
375
376 tmp_value = base_response_date;
377 date_value = (tmp_value > 0) ? tmp_value : 0;
378
379 // Deal with clock skew. Sigh.
380 //
381 // TODO solve this global clock problem
382 now_value = std::max(now, response_time);
383
384 ink_assert(response_time >= 0);
385 ink_assert(request_time >= 0);
386 ink_assert(response_time >= request_time);
387 ink_assert(now_value >= response_time);
388
389 if (date_value > 0) {
390 apparent_age = std::max(static_cast<time_t>(0), (response_time - date_value));
391 }
392 if (age_value < 0) {
393 current_age = -1; // Overflow from Age: header
394 } else {
395 corrected_received_age = std::max(apparent_age, age_value);
396 response_delay = response_time - request_time;
397 corrected_initial_age = corrected_received_age + response_delay;
398 resident_time = now_value - response_time;
399 current_age = corrected_initial_age + resident_time;
400 }
401
402 Debug("http_age", "[calculate_document_age] age_value: %" PRId64, (int64_t)age_value);
403 Debug("http_age", "[calculate_document_age] date_value: %" PRId64, (int64_t)date_value);
404 Debug("http_age", "[calculate_document_age] response_time: %" PRId64, (int64_t)response_time);
405 Debug("http_age", "[calculate_document_age] now: %" PRId64, (int64_t)now);
406 Debug("http_age", "[calculate_document_age] now (fixed): %" PRId64, (int64_t)now_value);
407 Debug("http_age", "[calculate_document_age] apparent_age: %" PRId64, (int64_t)apparent_age);
408 Debug("http_age", "[calculate_document_age] corrected_received_age: %" PRId64, (int64_t)corrected_received_age);
409 Debug("http_age", "[calculate_document_age] response_delay: %" PRId64, (int64_t)response_delay);
410 Debug("http_age", "[calculate_document_age] corrected_initial_age: %" PRId64, (int64_t)corrected_initial_age);
411 Debug("http_age", "[calculate_document_age] resident_time: %" PRId64, (int64_t)resident_time);
412 Debug("http_age", "[calculate_document_age] current_age: %" PRId64, (int64_t)current_age);
413
414 return current_age;
415 }
416
417 bool
does_server_allow_response_to_be_stored(HTTPHdr * resp)418 HttpTransactHeaders::does_server_allow_response_to_be_stored(HTTPHdr *resp)
419 {
420 uint32_t cc_mask = (MIME_COOKED_MASK_CC_NO_STORE | MIME_COOKED_MASK_CC_PRIVATE);
421
422 if ((resp->get_cooked_cc_mask() & cc_mask) || (resp->get_cooked_pragma_no_cache())) {
423 return false;
424 } else {
425 return true;
426 }
427 }
428
429 bool
downgrade_request(bool * origin_server_keep_alive,HTTPHdr * outgoing_request)430 HttpTransactHeaders::downgrade_request(bool *origin_server_keep_alive, HTTPHdr *outgoing_request)
431 {
432 // HTTPVersion ver;
433 /* First try turning keep_alive off */
434 if (*origin_server_keep_alive) {
435 *origin_server_keep_alive = false;
436 // ver.set(outgoing_request->version_get());
437 }
438
439 if (outgoing_request->version_get() == HTTPVersion(1, 1)) {
440 // ver.set (HTTPVersion (1, 0));
441 convert_to_1_0_request_header(outgoing_request);
442 } else {
443 return false;
444 }
445
446 return true;
447 }
448
449 void
generate_and_set_squid_codes(HTTPHdr * header,char * via_string,HttpTransact::SquidLogInfo * squid_codes)450 HttpTransactHeaders::generate_and_set_squid_codes(HTTPHdr *header, char *via_string, HttpTransact::SquidLogInfo *squid_codes)
451 {
452 SquidLogCode log_code = SQUID_LOG_EMPTY;
453 SquidHierarchyCode hier_code = SQUID_HIER_EMPTY;
454 SquidHitMissCode hit_miss_code = SQUID_HIT_RESERVED;
455
456 /////////////////////////////
457 // First the Hit-Miss Code //
458 /////////////////////////////
459 if ((via_string[VIA_DETAIL_CACHE_LOOKUP] == VIA_DETAIL_HIT_CONDITIONAL) ||
460 (via_string[VIA_DETAIL_CACHE_LOOKUP] == VIA_DETAIL_MISS_CONDITIONAL) ||
461 (via_string[VIA_DETAIL_CACHE_LOOKUP] == VIA_DETAIL_HIT_SERVED)) {
462 // its a cache hit.
463 if (via_string[VIA_CACHE_RESULT] == VIA_IN_RAM_CACHE_FRESH) {
464 hit_miss_code = SQUID_HIT_RAM;
465 } else { // TODO: Support other cache tiers here
466 hit_miss_code = SQUID_HIT_RESERVED;
467 }
468 } else {
469 int reason_len;
470 const char *reason = header->reason_get(&reason_len);
471
472 if (reason != nullptr && reason_len >= 24 && reason[0] == '!' && reason[1] == SQUID_HIT_RESERVED) {
473 hit_miss_code = SQUID_HIT_RESERVED;
474 // its a miss in the cache. find out why.
475 } else if (via_string[VIA_DETAIL_CACHE_LOOKUP] == VIA_DETAIL_MISS_EXPIRED) {
476 hit_miss_code = SQUID_MISS_PRE_EXPIRED;
477 } else if (via_string[VIA_DETAIL_CACHE_LOOKUP] == VIA_DETAIL_MISS_CONFIG) {
478 hit_miss_code = SQUID_MISS_NONE;
479 } else if (via_string[VIA_DETAIL_CACHE_LOOKUP] == VIA_DETAIL_MISS_CLIENT) {
480 hit_miss_code = SQUID_MISS_PRAGMA_NOCACHE;
481 } else if (via_string[VIA_DETAIL_CACHE_LOOKUP] == VIA_DETAIL_MISS_METHOD) {
482 hit_miss_code = SQUID_MISS_HTTP_NON_CACHE;
483 } else if (via_string[VIA_CLIENT_REQUEST] == VIA_CLIENT_ERROR) {
484 hit_miss_code = SQUID_MISS_ERROR;
485 } else if (via_string[VIA_CLIENT_REQUEST] == VIA_CLIENT_NO_CACHE) {
486 hit_miss_code = SQUID_MISS_PRAGMA_NOCACHE;
487 } else {
488 hit_miss_code = SQUID_MISS_NONE;
489 }
490 }
491
492 //////////////////////
493 // Now the Log Code //
494 //////////////////////
495 if (via_string[VIA_CLIENT_REQUEST] == VIA_CLIENT_NO_CACHE) {
496 log_code = SQUID_LOG_TCP_CLIENT_REFRESH;
497 }
498
499 else {
500 if (via_string[VIA_CLIENT_REQUEST] == VIA_CLIENT_IMS) {
501 if ((via_string[VIA_CACHE_RESULT] == VIA_IN_CACHE_FRESH) || (via_string[VIA_CACHE_RESULT] == VIA_IN_RAM_CACHE_FRESH)) {
502 log_code = SQUID_LOG_TCP_IMS_HIT;
503 } else {
504 if (via_string[VIA_CACHE_RESULT] == VIA_IN_CACHE_STALE && via_string[VIA_SERVER_RESULT] == VIA_SERVER_NOT_MODIFIED) {
505 log_code = SQUID_LOG_TCP_REFRESH_HIT;
506 } else {
507 log_code = SQUID_LOG_TCP_IMS_MISS;
508 }
509 }
510 }
511
512 else {
513 if (via_string[VIA_CACHE_RESULT] == VIA_IN_CACHE_STALE) {
514 if (via_string[VIA_SERVER_RESULT] == VIA_SERVER_NOT_MODIFIED) {
515 log_code = SQUID_LOG_TCP_REFRESH_HIT;
516 } else {
517 if (via_string[VIA_SERVER_RESULT] == VIA_SERVER_ERROR) {
518 log_code = SQUID_LOG_TCP_REF_FAIL_HIT;
519 } else {
520 log_code = SQUID_LOG_TCP_REFRESH_MISS;
521 }
522 }
523 } else {
524 if (via_string[VIA_CACHE_RESULT] == VIA_IN_CACHE_FRESH) {
525 log_code = SQUID_LOG_TCP_HIT;
526 } else if (via_string[VIA_CACHE_RESULT] == VIA_IN_RAM_CACHE_FRESH) {
527 log_code = SQUID_LOG_TCP_MEM_HIT;
528 } else {
529 log_code = SQUID_LOG_TCP_MISS;
530 }
531 }
532 }
533 }
534
535 ////////////////////////
536 // The Hierarchy Code //
537 ////////////////////////
538 if ((via_string[VIA_CACHE_RESULT] == VIA_IN_CACHE_FRESH) || (via_string[VIA_CACHE_RESULT] == VIA_IN_RAM_CACHE_FRESH)) {
539 hier_code = SQUID_HIER_NONE;
540 } else if (via_string[VIA_DETAIL_PP_CONNECT] == VIA_DETAIL_PP_SUCCESS) {
541 hier_code = SQUID_HIER_PARENT_HIT;
542 } else if (via_string[VIA_DETAIL_CACHE_TYPE] == VIA_DETAIL_PARENT) {
543 hier_code = SQUID_HIER_DEFAULT_PARENT;
544 } else if (via_string[VIA_DETAIL_TUNNEL] == VIA_DETAIL_TUNNEL_NO_FORWARD) {
545 hier_code = SQUID_HIER_NONE;
546 } else {
547 hier_code = SQUID_HIER_DIRECT;
548 }
549
550 // Errors may override the other codes, so check the via string error codes last
551 switch (via_string[VIA_ERROR_TYPE]) {
552 case VIA_ERROR_AUTHORIZATION:
553 log_code = SQUID_LOG_ERR_PROXY_DENIED;
554 break;
555 case VIA_ERROR_CONNECTION:
556 if (log_code == SQUID_LOG_TCP_MISS || log_code == SQUID_LOG_TCP_REFRESH_MISS) {
557 log_code = SQUID_LOG_ERR_CONNECT_FAIL;
558 }
559 break;
560 case VIA_ERROR_DNS_FAILURE:
561 log_code = SQUID_LOG_ERR_DNS_FAIL;
562 hier_code = SQUID_HIER_NONE;
563 break;
564 case VIA_ERROR_FORBIDDEN:
565 log_code = SQUID_LOG_ERR_PROXY_DENIED;
566 break;
567 case VIA_ERROR_HEADER_SYNTAX:
568 log_code = SQUID_LOG_ERR_INVALID_REQ;
569 hier_code = SQUID_HIER_NONE;
570 break;
571 case VIA_ERROR_SERVER:
572 if (log_code == SQUID_LOG_TCP_MISS || log_code == SQUID_LOG_TCP_IMS_MISS) {
573 log_code = SQUID_LOG_ERR_CONNECT_FAIL;
574 }
575 break;
576 case VIA_ERROR_TIMEOUT:
577 if (log_code == SQUID_LOG_TCP_MISS || log_code == SQUID_LOG_TCP_IMS_MISS) {
578 log_code = SQUID_LOG_ERR_READ_TIMEOUT;
579 }
580 if (hier_code == SQUID_HIER_PARENT_HIT) {
581 hier_code = SQUID_HIER_TIMEOUT_PARENT_HIT;
582 } else {
583 hier_code = SQUID_HIER_TIMEOUT_DIRECT;
584 }
585 break;
586 case VIA_ERROR_CACHE_READ:
587 log_code = SQUID_LOG_TCP_SWAPFAIL;
588 hier_code = SQUID_HIER_NONE;
589 break;
590 case VIA_ERROR_LOOP_DETECTED:
591 log_code = SQUID_LOG_ERR_LOOP_DETECTED;
592 hier_code = SQUID_HIER_NONE;
593 break;
594 default:
595 break;
596 }
597
598 squid_codes->log_code = log_code;
599 squid_codes->hier_code = hier_code;
600 squid_codes->hit_miss_code = hit_miss_code;
601 }
602
603 #include "HttpDebugNames.h"
604
605 void
insert_warning_header(HttpConfigParams * http_config_param,HTTPHdr * header,HTTPWarningCode code,const char * warn_text,int warn_text_len)606 HttpTransactHeaders::insert_warning_header(HttpConfigParams *http_config_param, HTTPHdr *header, HTTPWarningCode code,
607 const char *warn_text, int warn_text_len)
608 {
609 int bufsize, len;
610
611 // + 23 for 20 possible digits of warning code (long long max
612 // digits) & 2 spaces & the string terminator
613 bufsize = http_config_param->proxy_response_via_string_len + 23;
614 if (warn_text != nullptr) {
615 bufsize += warn_text_len;
616 } else {
617 warn_text_len = 0; // Make sure it's really zero
618 }
619
620 char *warning_text = static_cast<char *>(alloca(bufsize));
621
622 len =
623 snprintf(warning_text, bufsize, "%3d %s %.*s", code, http_config_param->proxy_response_via_string, warn_text_len, warn_text);
624 header->value_set(MIME_FIELD_WARNING, MIME_LEN_WARNING, warning_text, len);
625 }
626
627 void
insert_time_and_age_headers_in_response(ink_time_t request_sent_time,ink_time_t response_received_time,ink_time_t now,HTTPHdr * base,HTTPHdr * outgoing)628 HttpTransactHeaders::insert_time_and_age_headers_in_response(ink_time_t request_sent_time, ink_time_t response_received_time,
629 ink_time_t now, HTTPHdr *base, HTTPHdr *outgoing)
630 {
631 ink_time_t date = base->get_date();
632 ink_time_t current_age = calculate_document_age(request_sent_time, response_received_time, base, date, now);
633
634 outgoing->set_age(current_age); // set_age() deals with overflow properly, so pass it along
635
636 // FIX: should handle missing date when response is received, not here.
637 // See INKqa09852.
638 if (date <= 0) {
639 outgoing->set_date(now);
640 }
641 }
642
643 /// write the protocol stack to the @a via_string.
644 /// If @a detailed then do the full stack, otherwise just the "top level" protocol.
645 /// Returns the number of characters appended to hdr_string (no nul appended).
646 int
write_hdr_protocol_stack(char * hdr_string,size_t len,ProtocolStackDetail pSDetail,std::string_view * proto_buf,int n_proto,char separator)647 HttpTransactHeaders::write_hdr_protocol_stack(char *hdr_string, size_t len, ProtocolStackDetail pSDetail,
648 std::string_view *proto_buf, int n_proto, char separator)
649 {
650 char *hdr = hdr_string; // keep original pointer for size computation later.
651 char *limit = hdr_string + len;
652
653 if (n_proto <= 0 || hdr == nullptr || len <= 0) {
654 // nothing
655 } else if (ProtocolStackDetail::Full == pSDetail) {
656 for (std::string_view *v = proto_buf, *v_limit = proto_buf + n_proto; v < v_limit && (hdr + v->size() + 1) < limit; ++v) {
657 if (v != proto_buf) {
658 *hdr++ = separator;
659 }
660 memcpy(hdr, v->data(), v->size());
661 hdr += v->size();
662 }
663 } else {
664 std::string_view *proto_end = proto_buf + n_proto;
665 bool http_1_0_p = std::find(proto_buf, proto_end, IP_PROTO_TAG_HTTP_1_0) != proto_end;
666 bool http_1_1_p = std::find(proto_buf, proto_end, IP_PROTO_TAG_HTTP_1_1) != proto_end;
667
668 if ((http_1_0_p || http_1_1_p) && hdr + 10 < limit) {
669 bool tls_p = std::find_if(proto_buf, proto_end, [](std::string_view tag) { return IsPrefixOf("tls/"sv, tag); }) != proto_end;
670
671 memcpy(hdr, "http", 4);
672 hdr += 4;
673 if (tls_p) {
674 *hdr++ = 's';
675 }
676
677 // If detail level is compact (RFC 7239 compliant "proto" value for Forwarded field), stop here.
678
679 if (ProtocolStackDetail::Standard == pSDetail) {
680 *hdr++ = '/';
681 bool http_2_p = std::find(proto_buf, proto_end, IP_PROTO_TAG_HTTP_2_0) != proto_end;
682 if (http_2_p) {
683 *hdr++ = '2';
684 } else if (http_1_0_p) {
685 memcpy(hdr, "1.0", 3);
686 hdr += 3;
687 } else if (http_1_1_p) {
688 memcpy(hdr, "1.1", 3);
689 hdr += 3;
690 }
691 }
692 }
693 }
694 return hdr - hdr_string;
695 }
696
697 ///////////////////////////////////////////////////////////////////////////////
698 // Name : insert_via_header_in_request
699 // Description: takes in existing via_string and inserts it in header
700 //
701 // Input :
702 // Output :
703 //
704 // Details :
705 //
706 // [u<client-stuff> l<cache-lookup-stuff> o<server-stuff> f<cache-fill-stuff> p<proxy-stuff>]
707 //
708 // client stuff
709 // I IMS
710 // N no-cache
711 // A accept headers
712 // C cookie
713 //
714 // cache lookup stuff
715 // M miss
716 // A in cache, not acceptable
717 // S in cache, stale
718 // H in cache, fresh
719 //
720 // server stuff
721 // N not-modified
722 // S served
723 //
724 // cache fill stuff
725 // F filled into cache
726 // U updated cache
727 //
728 // proxy stuff
729 // N not-modified
730 // S served
731 // R origin server revalidated
732 //
733 // For example:
734 //
735 // [u lH o f pS] cache hit
736 // [u lM oS fF pS] cache miss
737 // [uN l oS f pS] no-cache origin server fetch
738 //
739 //
740 ///////////////////////////////////////////////////////////////////////////////
741 void
insert_via_header_in_request(HttpTransact::State * s,HTTPHdr * header)742 HttpTransactHeaders::insert_via_header_in_request(HttpTransact::State *s, HTTPHdr *header)
743 {
744 char new_via_string[1024]; // 512-bytes for hostname+via string, 512-bytes for the debug info
745 char *via_string = new_via_string;
746 char *via_limit = via_string + sizeof(new_via_string);
747
748 if ((s->http_config_param->proxy_hostname_len + s->http_config_param->proxy_request_via_string_len) > 512) {
749 header->value_append(MIME_FIELD_VIA, MIME_LEN_VIA, "TrafficServer", 13, true);
750 return;
751 }
752
753 char *incoming_via = s->via_string;
754 std::array<std::string_view, 10> proto_buf; // 10 seems like a reasonable number of protos to print
755 int n_proto = s->state_machine->populate_client_protocol(proto_buf.data(), proto_buf.size());
756
757 via_string +=
758 write_hdr_protocol_stack(via_string, via_limit - via_string, ProtocolStackDetail::Standard, proto_buf.data(), n_proto);
759 *via_string++ = ' ';
760
761 via_string += nstrcpy(via_string, s->http_config_param->proxy_hostname);
762
763 *via_string++ = '[';
764 memcpy(via_string, Machine::instance()->uuid.getString(), TS_UUID_STRING_LEN);
765 via_string += TS_UUID_STRING_LEN;
766 *via_string++ = ']';
767 *via_string++ = ' ';
768 *via_string++ = '(';
769
770 memcpy(via_string, s->http_config_param->proxy_request_via_string, s->http_config_param->proxy_request_via_string_len);
771 via_string += s->http_config_param->proxy_request_via_string_len;
772
773 if (s->txn_conf->insert_request_via_string > 1) {
774 *via_string++ = ' ';
775 *via_string++ = '[';
776
777 // incoming_via can be max MAX_VIA_INDICES+1 long (i.e. around 25 or so)
778 if (s->txn_conf->insert_request_via_string > 2) { // Highest verbosity
779 via_string += nstrcpy(via_string, incoming_via);
780 } else {
781 memcpy(via_string, incoming_via + VIA_CLIENT, VIA_SERVER - VIA_CLIENT);
782 via_string += VIA_SERVER - VIA_CLIENT;
783 }
784 *via_string++ = ']';
785
786 // reserve 4 for " []" and 3 for "])".
787 if (via_limit - via_string > 4 && s->txn_conf->insert_request_via_string > 3) { // Ultra highest verbosity
788 *via_string++ = ' ';
789 *via_string++ = '[';
790 via_string +=
791 write_hdr_protocol_stack(via_string, via_limit - via_string - 3, ProtocolStackDetail::Full, proto_buf.data(), n_proto);
792 *via_string++ = ']';
793 }
794 }
795
796 *via_string++ = ')';
797 *via_string = 0;
798
799 ink_assert((size_t)(via_string - new_via_string) < (sizeof(new_via_string) - 1));
800 header->value_append(MIME_FIELD_VIA, MIME_LEN_VIA, new_via_string, via_string - new_via_string, true);
801 }
802
803 void
insert_hsts_header_in_response(HttpTransact::State * s,HTTPHdr * header)804 HttpTransactHeaders::insert_hsts_header_in_response(HttpTransact::State *s, HTTPHdr *header)
805 {
806 char new_hsts_string[64];
807 char *hsts_string = new_hsts_string;
808 constexpr char include_subdomains[] = "; includeSubDomains";
809
810 // add max-age
811 int length = snprintf(new_hsts_string, sizeof(new_hsts_string), "max-age=%" PRId64, s->txn_conf->proxy_response_hsts_max_age);
812
813 // add include subdomain if set
814 if (s->txn_conf->proxy_response_hsts_include_subdomains) {
815 hsts_string += length;
816 memcpy(hsts_string, include_subdomains, sizeof(include_subdomains) - 1);
817 length += sizeof(include_subdomains) - 1;
818 }
819
820 header->value_set(MIME_FIELD_STRICT_TRANSPORT_SECURITY, MIME_LEN_STRICT_TRANSPORT_SECURITY, new_hsts_string, length);
821 }
822
823 void
insert_via_header_in_response(HttpTransact::State * s,HTTPHdr * header)824 HttpTransactHeaders::insert_via_header_in_response(HttpTransact::State *s, HTTPHdr *header)
825 {
826 char new_via_string[1024]; // 512-bytes for hostname+via string, 512-bytes for the debug info
827 char *via_string = new_via_string;
828 char *via_limit = via_string + sizeof(new_via_string);
829
830 if ((s->http_config_param->proxy_hostname_len + s->http_config_param->proxy_response_via_string_len) > 512) {
831 header->value_append(MIME_FIELD_VIA, MIME_LEN_VIA, "TrafficServer", 13, true);
832 return;
833 }
834
835 char *incoming_via = s->via_string;
836 std::array<std::string_view, 10> proto_buf; // 10 seems like a reasonable number of protos to print
837 int n_proto = 0;
838
839 // Should suffice - if we're adding a response VIA, the connection is HTTP and only 1.0 and 1.1 are supported outbound.
840 // TODO H2 expand for HTTP/2 outbound
841 proto_buf[n_proto++] = header->version_get().get_minor() == 0 ? IP_PROTO_TAG_HTTP_1_0 : IP_PROTO_TAG_HTTP_1_1;
842
843 auto ss = s->state_machine->get_server_session();
844 if (ss) {
845 n_proto += ss->populate_protocol(proto_buf.data() + n_proto, proto_buf.size() - n_proto);
846 }
847 via_string +=
848 write_hdr_protocol_stack(via_string, via_limit - via_string, ProtocolStackDetail::Standard, proto_buf.data(), n_proto);
849 *via_string++ = ' ';
850
851 via_string += nstrcpy(via_string, s->http_config_param->proxy_hostname);
852 *via_string++ = ' ';
853 *via_string++ = '(';
854
855 memcpy(via_string, s->http_config_param->proxy_response_via_string, s->http_config_param->proxy_response_via_string_len);
856 via_string += s->http_config_param->proxy_response_via_string_len;
857
858 if (s->txn_conf->insert_response_via_string > 1) {
859 *via_string++ = ' ';
860 *via_string++ = '[';
861
862 // incoming_via can be max MAX_VIA_INDICES+1 long (i.e. around 25 or so)
863 if (s->txn_conf->insert_response_via_string > 2) { // Highest verbosity
864 via_string += nstrcpy(via_string, incoming_via);
865 } else {
866 memcpy(via_string, incoming_via + VIA_CACHE, VIA_PROXY - VIA_CACHE);
867 via_string += VIA_PROXY - VIA_CACHE;
868 }
869 *via_string++ = ']';
870
871 if (via_limit - via_string > 4 && s->txn_conf->insert_response_via_string > 3) { // Ultra highest verbosity
872 *via_string++ = ' ';
873 *via_string++ = '[';
874 via_string +=
875 write_hdr_protocol_stack(via_string, via_limit - via_string - 3, ProtocolStackDetail::Full, proto_buf.data(), n_proto);
876 *via_string++ = ']';
877 }
878 }
879
880 *via_string++ = ')';
881 *via_string = 0;
882
883 ink_assert((size_t)(via_string - new_via_string) < (sizeof(new_via_string) - 1));
884 header->value_append(MIME_FIELD_VIA, MIME_LEN_VIA, new_via_string, via_string - new_via_string, true);
885 }
886
887 void
remove_conditional_headers(HTTPHdr * outgoing)888 HttpTransactHeaders::remove_conditional_headers(HTTPHdr *outgoing)
889 {
890 if (outgoing->presence(MIME_PRESENCE_IF_MODIFIED_SINCE | MIME_PRESENCE_IF_UNMODIFIED_SINCE | MIME_PRESENCE_IF_MATCH |
891 MIME_PRESENCE_IF_NONE_MATCH)) {
892 outgoing->field_delete(MIME_FIELD_IF_MODIFIED_SINCE, MIME_LEN_IF_MODIFIED_SINCE);
893 outgoing->field_delete(MIME_FIELD_IF_UNMODIFIED_SINCE, MIME_LEN_IF_UNMODIFIED_SINCE);
894 outgoing->field_delete(MIME_FIELD_IF_MATCH, MIME_LEN_IF_MATCH);
895 outgoing->field_delete(MIME_FIELD_IF_NONE_MATCH, MIME_LEN_IF_NONE_MATCH);
896 }
897 // TODO: how about RANGE and IF_RANGE?
898 }
899
900 void
remove_100_continue_headers(HttpTransact::State * s,HTTPHdr * outgoing)901 HttpTransactHeaders::remove_100_continue_headers(HttpTransact::State *s, HTTPHdr *outgoing)
902 {
903 int len = 0;
904 const char *expect = s->hdr_info.client_request.value_get(MIME_FIELD_EXPECT, MIME_LEN_EXPECT, &len);
905
906 if ((len == HTTP_LEN_100_CONTINUE) && (strncasecmp(expect, HTTP_VALUE_100_CONTINUE, HTTP_LEN_100_CONTINUE) == 0)) {
907 outgoing->field_delete(MIME_FIELD_EXPECT, MIME_LEN_EXPECT);
908 }
909 }
910
911 ////////////////////////////////////////////////////////////////////////
912 // Deal with lame-o servers by removing the host name from the url.
913 void
remove_host_name_from_url(HTTPHdr * outgoing_request)914 HttpTransactHeaders::remove_host_name_from_url(HTTPHdr *outgoing_request)
915 {
916 URL *outgoing_url = outgoing_request->url_get();
917 outgoing_url->nuke_proxy_stuff();
918 }
919
920 void
add_global_user_agent_header_to_request(const OverridableHttpConfigParams * http_txn_conf,HTTPHdr * header)921 HttpTransactHeaders::add_global_user_agent_header_to_request(const OverridableHttpConfigParams *http_txn_conf, HTTPHdr *header)
922 {
923 if (http_txn_conf->global_user_agent_header) {
924 MIMEField *ua_field;
925
926 Debug("http_trans", "Adding User-Agent: %.*s", static_cast<int>(http_txn_conf->global_user_agent_header_size),
927 http_txn_conf->global_user_agent_header);
928 if ((ua_field = header->field_find(MIME_FIELD_USER_AGENT, MIME_LEN_USER_AGENT)) == nullptr) {
929 if (likely((ua_field = header->field_create(MIME_FIELD_USER_AGENT, MIME_LEN_USER_AGENT)) != nullptr)) {
930 header->field_attach(ua_field);
931 }
932 }
933 // This will remove any old string (free it), and set our User-Agent.
934 if (likely(ua_field)) {
935 header->field_value_set(ua_field, http_txn_conf->global_user_agent_header, http_txn_conf->global_user_agent_header_size);
936 }
937 }
938 }
939
940 void
add_forwarded_field_to_request(HttpTransact::State * s,HTTPHdr * request)941 HttpTransactHeaders::add_forwarded_field_to_request(HttpTransact::State *s, HTTPHdr *request)
942 {
943 HttpForwarded::OptionBitSet optSet = s->txn_conf->insert_forwarded;
944
945 if (optSet.any()) { // One or more Forwarded parameters enabled, so insert/append to Forwarded header.
946
947 ts::LocalBufferWriter<1024> hdr;
948
949 if (optSet[HttpForwarded::FOR] and ats_is_ip(&s->client_info.src_addr.sa)) {
950 // NOTE: The logic within this if statement assumes that hdr is empty at this point.
951
952 hdr << "for=";
953
954 bool is_ipv6 = ats_is_ip6(&s->client_info.src_addr.sa);
955
956 if (is_ipv6) {
957 hdr << "\"[";
958 }
959
960 if (ats_ip_ntop(&s->client_info.src_addr.sa, hdr.auxBuffer(), hdr.remaining()) == nullptr) {
961 Debug("http_trans", "[add_forwarded_field_to_outgoing_request] ats_ip_ntop() call failed");
962 return;
963 }
964
965 // Fail-safe.
966 hdr.auxBuffer()[hdr.remaining() - 1] = '\0';
967
968 hdr.fill(strlen(hdr.auxBuffer()));
969
970 if (is_ipv6) {
971 hdr << "]\"";
972 }
973 }
974
975 if (optSet[HttpForwarded::BY_UNKNOWN]) {
976 if (hdr.size()) {
977 hdr << ';';
978 }
979
980 hdr << "by=unknown";
981 }
982
983 if (optSet[HttpForwarded::BY_SERVER_NAME]) {
984 if (hdr.size()) {
985 hdr << ';';
986 }
987
988 hdr << "by=" << s->http_config_param->proxy_hostname;
989 }
990
991 const Machine &m = *Machine::instance();
992
993 if (optSet[HttpForwarded::BY_UUID] and m.uuid.valid()) {
994 if (hdr.size()) {
995 hdr << ';';
996 }
997
998 hdr << "by=_" << m.uuid.getString();
999 }
1000
1001 if (optSet[HttpForwarded::BY_IP] and (m.ip_string_len > 0)) {
1002 if (hdr.size()) {
1003 hdr << ';';
1004 }
1005
1006 hdr << "by=";
1007
1008 bool is_ipv6 = ats_is_ip6(&s->client_info.dst_addr.sa);
1009
1010 if (is_ipv6) {
1011 hdr << "\"[";
1012 }
1013
1014 if (ats_ip_ntop(&s->client_info.dst_addr.sa, hdr.auxBuffer(), hdr.remaining()) == nullptr) {
1015 Debug("http_trans", "[add_forwarded_field_to_outgoing_request] ats_ip_ntop() call failed");
1016 return;
1017 }
1018
1019 // Fail-safe.
1020 hdr.auxBuffer()[hdr.remaining() - 1] = '\0';
1021
1022 hdr.fill(strlen(hdr.auxBuffer()));
1023
1024 if (is_ipv6) {
1025 hdr << "]\"";
1026 }
1027 }
1028
1029 std::array<std::string_view, 10> protoBuf; // 10 seems like a reasonable number of protos to print
1030 int n_proto = 0; // Indulge clang's incorrect claim that this need to be initialized.
1031
1032 static const HttpForwarded::OptionBitSet OptionsNeedingProtocol = HttpForwarded::OptionBitSet()
1033 .set(HttpForwarded::PROTO)
1034 .set(HttpForwarded::CONNECTION_COMPACT)
1035 .set(HttpForwarded::CONNECTION_STD)
1036 .set(HttpForwarded::CONNECTION_FULL);
1037
1038 if ((optSet bitand OptionsNeedingProtocol).any()) {
1039 n_proto = s->state_machine->populate_client_protocol(protoBuf.data(), protoBuf.size());
1040 }
1041
1042 if (optSet[HttpForwarded::PROTO] and (n_proto > 0)) {
1043 if (hdr.size()) {
1044 hdr << ';';
1045 }
1046
1047 hdr << "proto=";
1048
1049 int numChars = HttpTransactHeaders::write_hdr_protocol_stack(hdr.auxBuffer(), hdr.remaining(), ProtocolStackDetail::Compact,
1050 protoBuf.data(), n_proto, '-');
1051 if (numChars > 0) {
1052 hdr.fill(size_t(numChars));
1053 }
1054 }
1055
1056 if (optSet[HttpForwarded::HOST]) {
1057 const MIMEField *hostField = s->hdr_info.client_request.field_find(MIME_FIELD_HOST, MIME_LEN_HOST);
1058
1059 if (hostField and hostField->m_len_value) {
1060 std::string_view hSV{hostField->m_ptr_value, hostField->m_len_value};
1061
1062 bool needsDoubleQuotes = hSV.find(':') != std::string_view::npos;
1063
1064 if (hdr.size()) {
1065 hdr << ';';
1066 }
1067
1068 hdr << "host=";
1069 if (needsDoubleQuotes) {
1070 hdr << '"';
1071 }
1072 hdr << hSV;
1073 if (needsDoubleQuotes) {
1074 hdr << '"';
1075 }
1076 }
1077 }
1078
1079 if (n_proto > 0) {
1080 auto Conn = [&](HttpForwarded::Option opt, HttpTransactHeaders::ProtocolStackDetail detail) -> void {
1081 if (optSet[opt] && hdr.remaining() > 0) {
1082 ts::FixedBufferWriter lw{hdr.auxBuffer(), hdr.remaining()};
1083
1084 if (hdr.size()) {
1085 lw << ';';
1086 }
1087
1088 lw << "connection=";
1089
1090 int numChars =
1091 HttpTransactHeaders::write_hdr_protocol_stack(lw.auxBuffer(), lw.remaining(), detail, protoBuf.data(), n_proto, '-');
1092 if (numChars > 0 && !lw.fill(size_t(numChars)).error()) {
1093 hdr.fill(lw.size());
1094 }
1095 }
1096 };
1097
1098 Conn(HttpForwarded::CONNECTION_COMPACT, HttpTransactHeaders::ProtocolStackDetail::Compact);
1099 Conn(HttpForwarded::CONNECTION_STD, HttpTransactHeaders::ProtocolStackDetail::Standard);
1100 Conn(HttpForwarded::CONNECTION_FULL, HttpTransactHeaders::ProtocolStackDetail::Full);
1101 }
1102
1103 // Add or append to the Forwarded header. As a fail-safe against corrupting the MIME header, don't add Forwarded if
1104 // it's size is exactly the capacity of the buffer.
1105 //
1106 if (hdr.size() and !hdr.error() and (hdr.size() < hdr.capacity())) {
1107 std::string_view sV = hdr.view();
1108
1109 request->value_append(MIME_FIELD_FORWARDED, MIME_LEN_FORWARDED, sV.data(), sV.size(), true, ','); // true => separator must
1110 // be inserted
1111
1112 Debug("http_trans", "[add_forwarded_field_to_outgoing_request] Forwarded header (%.*s) added", static_cast<int>(hdr.size()),
1113 hdr.data());
1114 }
1115 }
1116
1117 } // end HttpTransact::add_forwarded_field_to_outgoing_request()
1118
1119 void
add_server_header_to_response(const OverridableHttpConfigParams * http_txn_conf,HTTPHdr * header)1120 HttpTransactHeaders::add_server_header_to_response(const OverridableHttpConfigParams *http_txn_conf, HTTPHdr *header)
1121 {
1122 if (http_txn_conf->proxy_response_server_enabled && http_txn_conf->proxy_response_server_string) {
1123 MIMEField *ua_field;
1124 bool do_add = true;
1125
1126 if ((ua_field = header->field_find(MIME_FIELD_SERVER, MIME_LEN_SERVER)) == nullptr) {
1127 if (likely((ua_field = header->field_create(MIME_FIELD_SERVER, MIME_LEN_SERVER)) != nullptr)) {
1128 header->field_attach(ua_field);
1129 }
1130 } else {
1131 // There was an existing header from Origin, so only add if setting allows to overwrite.
1132 do_add = (1 == http_txn_conf->proxy_response_server_enabled);
1133 }
1134
1135 // This will remove any old string (free it), and set our Server header.
1136 if (do_add && likely(ua_field)) {
1137 Debug("http_trans", "Adding Server: %s", http_txn_conf->proxy_response_server_string);
1138 header->field_value_set(ua_field, http_txn_conf->proxy_response_server_string,
1139 http_txn_conf->proxy_response_server_string_len);
1140 }
1141 }
1142 }
1143
1144 void
remove_privacy_headers_from_request(HttpConfigParams * http_config_param,const OverridableHttpConfigParams * http_txn_conf,HTTPHdr * header)1145 HttpTransactHeaders::remove_privacy_headers_from_request(HttpConfigParams *http_config_param,
1146 const OverridableHttpConfigParams *http_txn_conf, HTTPHdr *header)
1147 {
1148 if (!header) {
1149 return;
1150 }
1151
1152 // From
1153 if (http_txn_conf->anonymize_remove_from) {
1154 Debug("anon", "removing 'From' headers");
1155 header->field_delete(MIME_FIELD_FROM, MIME_LEN_FROM);
1156 }
1157 // Referer
1158 if (http_txn_conf->anonymize_remove_referer) {
1159 Debug("anon", "removing 'Referer' headers");
1160 header->field_delete(MIME_FIELD_REFERER, MIME_LEN_REFERER);
1161 }
1162 // User-Agent
1163 if (http_txn_conf->anonymize_remove_user_agent) {
1164 Debug("anon", "removing 'User-agent' headers");
1165 header->field_delete(MIME_FIELD_USER_AGENT, MIME_LEN_USER_AGENT);
1166 }
1167 // Cookie
1168 if (http_txn_conf->anonymize_remove_cookie) {
1169 Debug("anon", "removing 'Cookie' headers");
1170 header->field_delete(MIME_FIELD_COOKIE, MIME_LEN_COOKIE);
1171 }
1172 // Client-ip
1173 if (http_txn_conf->anonymize_remove_client_ip) {
1174 Debug("anon", "removing 'Client-ip' headers");
1175 header->field_delete(MIME_FIELD_CLIENT_IP, MIME_LEN_CLIENT_IP);
1176 }
1177 /////////////////////////////////////////////
1178 // remove any other user specified headers //
1179 /////////////////////////////////////////////
1180
1181 // FIXME: we shouldn't parse this list every time, only when the
1182 // FIXME: config file changes.
1183 if (http_config_param->anonymize_other_header_list) {
1184 Str *field;
1185 StrList anon_list(false);
1186 const char *anon_string;
1187
1188 anon_string = http_config_param->anonymize_other_header_list;
1189 Debug("anon", "removing other headers (%s)", anon_string);
1190 HttpCompat::parse_comma_list(&anon_list, anon_string);
1191 for (field = anon_list.head; field != nullptr; field = field->next) {
1192 Debug("anon", "removing '%s' headers", field->str);
1193 header->field_delete(field->str, field->len);
1194 }
1195 }
1196 }
1197
1198 void
normalize_accept_encoding(const OverridableHttpConfigParams * ohcp,HTTPHdr * header)1199 HttpTransactHeaders::normalize_accept_encoding(const OverridableHttpConfigParams *ohcp, HTTPHdr *header)
1200 {
1201 int normalize_ae = ohcp->normalize_ae;
1202
1203 if (normalize_ae) {
1204 MIMEField *ae_field = header->field_find(MIME_FIELD_ACCEPT_ENCODING, MIME_LEN_ACCEPT_ENCODING);
1205
1206 if (ae_field) {
1207 if (normalize_ae == 1) {
1208 // Force Accept-Encoding header to gzip or no header.
1209 if (HttpTransactCache::match_content_encoding(ae_field, "gzip")) {
1210 header->field_value_set(ae_field, "gzip", 4);
1211 Debug("http_trans", "[Headers::normalize_accept_encoding] normalized Accept-Encoding to gzip");
1212 } else {
1213 header->field_delete(ae_field);
1214 Debug("http_trans", "[Headers::normalize_accept_encoding] removed non-gzip Accept-Encoding");
1215 }
1216 } else if (normalize_ae == 2) {
1217 // Force Accept-Encoding header to br (Brotli) or no header.
1218 if (HttpTransactCache::match_content_encoding(ae_field, "br")) {
1219 header->field_value_set(ae_field, "br", 2);
1220 Debug("http_trans", "[Headers::normalize_accept_encoding] normalized Accept-Encoding to br");
1221 } else if (HttpTransactCache::match_content_encoding(ae_field, "gzip")) {
1222 header->field_value_set(ae_field, "gzip", 4);
1223 Debug("http_trans", "[Headers::normalize_accept_encoding] normalized Accept-Encoding to gzip");
1224 } else {
1225 header->field_delete(ae_field);
1226 Debug("http_trans", "[Headers::normalize_accept_encoding] removed non-br Accept-Encoding");
1227 }
1228 } else {
1229 static bool logged = false;
1230
1231 if (!logged) {
1232 Error("proxy.config.http.normalize_ae value out of range");
1233 logged = true;
1234 }
1235 }
1236 }
1237 }
1238 }
1239
1240 void
add_connection_close(HTTPHdr * header)1241 HttpTransactHeaders::add_connection_close(HTTPHdr *header)
1242 {
1243 MIMEField *field = header->field_find(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION);
1244 if (!field) {
1245 field = header->field_create(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION);
1246 header->field_attach(field);
1247 }
1248 header->field_value_set(field, HTTP_VALUE_CLOSE, HTTP_LEN_CLOSE);
1249 }
1250