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 /****************************************************************************
25
26 HttpBodyFactory.cc
27
28
29 ****************************************************************************/
30
31 #include "tscore/ink_platform.h"
32 #include "tscore/ink_sprintf.h"
33 #include "tscore/ink_file.h"
34 #include "tscore/Filenames.h"
35 #include "HttpBodyFactory.h"
36 #include <unistd.h>
37 #include <dirent.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include "URL.h"
41 #include "logging/Log.h"
42 #include "logging/LogAccess.h"
43 #include "HttpCompat.h"
44 #include "tscore/I_Layout.h"
45
46 //////////////////////////////////////////////////////////////////////
47 // The HttpBodyFactory creates HTTP response page bodies, supported //
48 // configurable customization and language-targeting. //
49 // //
50 // The body factory can be reconfigured dynamically by a manager //
51 // callback, so locking is required. The callback takes a lock, //
52 // and the user entry points take a lock. These locks may limit //
53 // the speed of error page generation. //
54 //////////////////////////////////////////////////////////////////////
55
56 ////////////////////////////////////////////////////////////////////////
57 //
58 // User-Callable APIs --- locks will be taken internally
59 //
60 ////////////////////////////////////////////////////////////////////////
61
62 char *
fabricate_with_old_api(const char * type,HttpTransact::State * context,int64_t max_buffer_length,int64_t * resulting_buffer_length,char * content_language_out_buf,size_t content_language_buf_size,char * content_type_out_buf,size_t content_type_buf_size,int format_size,const char * format)63 HttpBodyFactory::fabricate_with_old_api(const char *type, HttpTransact::State *context, int64_t max_buffer_length,
64 int64_t *resulting_buffer_length, char *content_language_out_buf,
65 size_t content_language_buf_size, char *content_type_out_buf, size_t content_type_buf_size,
66 int format_size, const char *format)
67 {
68 char *buffer = nullptr;
69 const char *lang_ptr = nullptr;
70 const char *charset_ptr = nullptr;
71 char url[1024];
72 const char *set = nullptr;
73 bool found_requested_template = false;
74 bool plain_flag = false;
75
76 ///////////////////////////////////////////////
77 // if suppressing this response, return NULL //
78 ///////////////////////////////////////////////
79 if (is_response_suppressed(context)) {
80 if (enable_logging) {
81 Log::error("BODY_FACTORY: suppressing '%s' response for url '%s'", type, url);
82 }
83 return nullptr;
84 }
85
86 lock();
87
88 *resulting_buffer_length = 0;
89
90 ink_strlcpy(content_language_out_buf, "en", content_language_buf_size);
91 ink_strlcpy(content_type_out_buf, "text/html", content_type_buf_size);
92
93 ///////////////////////////////////////////////////////////////////
94 // if logging turned on, buffer up the URL string for simplicity //
95 ///////////////////////////////////////////////////////////////////
96 if (enable_logging) {
97 url[0] = '\0';
98 URL *u = context->hdr_info.client_request.url_get();
99
100 if (u->valid()) { /* if url exists, copy the string into buffer */
101 size_t i;
102 char *s = u->string_get(&context->arena);
103 for (i = 0; (i < sizeof(url) - 1) && s[i]; i++) {
104 url[i] = s[i];
105 }
106 url[i] = '\0';
107 if (s) {
108 context->arena.str_free(s);
109 }
110 }
111 }
112 //////////////////////////////////////////////////////////////////////////////////
113 // if language-targeting activated, get client Accept-Language & Accept-Charset //
114 //////////////////////////////////////////////////////////////////////////////////
115
116 StrList acpt_language_list(false);
117 StrList acpt_charset_list(false);
118
119 if (enable_customizations == 2) {
120 context->hdr_info.client_request.value_get_comma_list(MIME_FIELD_ACCEPT_LANGUAGE, MIME_LEN_ACCEPT_LANGUAGE,
121 &acpt_language_list);
122 context->hdr_info.client_request.value_get_comma_list(MIME_FIELD_ACCEPT_CHARSET, MIME_LEN_ACCEPT_CHARSET, &acpt_charset_list);
123 }
124 ///////////////////////////////////////////
125 // check if we don't need to format body //
126 ///////////////////////////////////////////
127
128 buffer = (format == nullptr) ? nullptr : ats_strndup(format, format_size);
129 if (buffer != nullptr && format_size > 0) {
130 *resulting_buffer_length = format_size > max_buffer_length ? 0 : format_size;
131 plain_flag = true;
132 }
133 /////////////////////////////////////////////////////////
134 // try to fabricate the desired type of error response //
135 /////////////////////////////////////////////////////////
136 if (buffer == nullptr) {
137 buffer =
138 fabricate(&acpt_language_list, &acpt_charset_list, type, context, resulting_buffer_length, &lang_ptr, &charset_ptr, &set);
139 found_requested_template = (buffer != nullptr);
140 }
141 /////////////////////////////////////////////////////////////
142 // if failed, try to fabricate the default custom response //
143 /////////////////////////////////////////////////////////////
144 if (buffer == nullptr) {
145 if (is_response_body_precluded(context->http_return_code)) {
146 *resulting_buffer_length = 0;
147 unlock();
148 return nullptr;
149 }
150 buffer = fabricate(&acpt_language_list, &acpt_charset_list, "default", context, resulting_buffer_length, &lang_ptr,
151 &charset_ptr, &set);
152 }
153
154 ///////////////////////////////////
155 // enforce the max buffer length //
156 ///////////////////////////////////
157 if (buffer && (*resulting_buffer_length > max_buffer_length)) {
158 if (enable_logging) {
159 Log::error(("BODY_FACTORY: template '%s/%s' consumed %" PRId64 " bytes, "
160 "exceeding %" PRId64 " byte limit, using internal default"),
161 set, type, *resulting_buffer_length, max_buffer_length);
162 }
163 *resulting_buffer_length = 0;
164 buffer = static_cast<char *>(ats_free_null(buffer));
165 }
166 /////////////////////////////////////////////////////////////////////
167 // handle return of instantiated template and generate the content //
168 // language and content type return values //
169 /////////////////////////////////////////////////////////////////////
170 if (buffer) { // got an instantiated template
171 if (!plain_flag) {
172 snprintf(content_language_out_buf, content_language_buf_size, "%s", lang_ptr);
173 snprintf(content_type_out_buf, content_type_buf_size, "text/html; charset=%s", charset_ptr);
174 }
175
176 if (enable_logging) {
177 if (found_requested_template) { // got exact template
178 Log::error(("BODY_FACTORY: using custom template "
179 "'%s/%s' for url '%s' (language '%s', charset '%s')"),
180 set, type, url, lang_ptr, charset_ptr);
181 } else { // got default template
182 Log::error(("BODY_FACTORY: can't find custom template "
183 "'%s/%s', using '%s/%s' for url '%s' (language '%s', charset '%s')"),
184 set, type, set, "default", url, lang_ptr, charset_ptr);
185 }
186 }
187 } else { // no template
188 if (enable_logging) {
189 Log::error(("BODY_FACTORY: can't find templates '%s' or '%s' for url `%s'"), type, "default", url);
190 }
191 }
192 unlock();
193
194 return buffer;
195 }
196
197 void
dump_template_tables(FILE * fp)198 HttpBodyFactory::dump_template_tables(FILE *fp)
199 {
200 lock();
201 if (table_of_sets) {
202 for (const auto &it1 : *table_of_sets.get()) {
203 HttpBodySet *body_set = static_cast<HttpBodySet *>(it1.second);
204 if (body_set) {
205 fprintf(fp, "set %s: name '%s', lang '%s', charset '%s'\n", it1.first.c_str(), body_set->set_name,
206 body_set->content_language, body_set->content_charset);
207
208 ///////////////////////////////////////////
209 // loop over body-types->body hash table //
210 ///////////////////////////////////////////
211
212 ink_assert(body_set->is_sane());
213 if (body_set->table_of_pages) {
214 for (const auto &it2 : *body_set->table_of_pages.get()) {
215 fprintf(fp, " %-30s: %" PRId64 " bytes\n", it2.first.c_str(), it2.second->byte_count);
216 }
217 }
218 }
219 }
220 }
221
222 unlock();
223 }
224
225 ////////////////////////////////////////////////////////////////////////
226 //
227 // Configuration Change Callback
228 //
229 ////////////////////////////////////////////////////////////////////////
230
231 static int
config_callback(const char *,RecDataT,RecData,void * cookie)232 config_callback(const char * /* name ATS_UNUSED */, RecDataT /* data_type ATS_UNUSED */, RecData /* data ATS_UNUSED */,
233 void *cookie)
234 {
235 HttpBodyFactory *body_factory = static_cast<HttpBodyFactory *>(cookie);
236 body_factory->reconfigure();
237 return 0;
238 }
239
240 void
reconfigure()241 HttpBodyFactory::reconfigure()
242 {
243 RecInt e;
244 RecString s = nullptr;
245 bool all_found;
246 int rec_err;
247
248 lock();
249 sanity_check();
250
251 if (!callbacks_established) {
252 unlock();
253 return;
254 } // callbacks not setup right
255
256 ////////////////////////////////////////////
257 // extract relevant records.config values //
258 ////////////////////////////////////////////
259
260 Debug("body_factory", "config variables changed, reconfiguring...");
261
262 all_found = true;
263
264 // enable_customizations if records.config set
265 rec_err = RecGetRecordInt("proxy.config.body_factory.enable_customizations", &e);
266 enable_customizations = ((rec_err == REC_ERR_OKAY) ? e : 0);
267 all_found = all_found && (rec_err == REC_ERR_OKAY);
268 Debug("body_factory", "enable_customizations = %d (found = %" PRId64 ")", enable_customizations, e);
269
270 rec_err = RecGetRecordInt("proxy.config.body_factory.enable_logging", &e);
271 enable_logging = ((rec_err == REC_ERR_OKAY) ? (e ? true : false) : false);
272 all_found = all_found && (rec_err == REC_ERR_OKAY);
273 Debug("body_factory", "enable_logging = %d (found = %" PRId64 ")", enable_logging, e);
274
275 rec_err = RecGetRecordInt("proxy.config.body_factory.response_suppression_mode", &e);
276 response_suppression_mode = ((rec_err == REC_ERR_OKAY) ? e : 0);
277 all_found = all_found && (rec_err == REC_ERR_OKAY);
278 Debug("body_factory", "response_suppression_mode = %d (found = %" PRId64 ")", response_suppression_mode, e);
279
280 ats_scoped_str directory_of_template_sets;
281
282 rec_err = RecGetRecordString_Xmalloc("proxy.config.body_factory.template_sets_dir", &s);
283 all_found = all_found && (rec_err == REC_ERR_OKAY);
284 if (rec_err == REC_ERR_OKAY) {
285 directory_of_template_sets = Layout::get()->relative(s);
286 if (access(directory_of_template_sets, R_OK) < 0) {
287 Warning("Unable to access() directory '%s': %d, %s", (const char *)directory_of_template_sets, errno, strerror(errno));
288 Warning(" Please set 'proxy.config.body_factory.template_sets_dir' ");
289 }
290 }
291
292 Debug("body_factory", "directory_of_template_sets = '%s' ", (const char *)directory_of_template_sets);
293
294 ats_free(s);
295
296 if (!all_found) {
297 Warning("config changed, but can't fetch all proxy.config.body_factory values");
298 }
299
300 /////////////////////////////////////////////
301 // clear out previous template hash tables //
302 /////////////////////////////////////////////
303
304 nuke_template_tables();
305
306 /////////////////////////////////////////////////////////////
307 // at this point, the body hash table is gone, so we start //
308 // building a new one, by scanning the template directory. //
309 /////////////////////////////////////////////////////////////
310
311 if (directory_of_template_sets) {
312 table_of_sets = load_sets_from_directory(directory_of_template_sets);
313 }
314
315 unlock();
316 }
317
318 ////////////////////////////////////////////////////////////////////////
319 //
320 // class HttpBodyFactory
321 //
322 ////////////////////////////////////////////////////////////////////////
323
HttpBodyFactory()324 HttpBodyFactory::HttpBodyFactory()
325 {
326 int i;
327 bool status, no_registrations_failed;
328
329 ////////////////////////////////////
330 // initialize first-time defaults //
331 ////////////////////////////////////
332 ink_mutex_init(&mutex);
333
334 //////////////////////////////////////////////////////
335 // set up management configuration-change callbacks //
336 //////////////////////////////////////////////////////
337
338 static const char *config_record_names[] = {
339 "proxy.config.body_factory.enable_customizations", "proxy.config.body_factory.enable_logging",
340 "proxy.config.body_factory.template_sets_dir", "proxy.config.body_factory.response_suppression_mode", nullptr};
341
342 no_registrations_failed = true;
343 for (i = 0; config_record_names[i] != nullptr; i++) {
344 status = REC_RegisterConfigUpdateFunc(config_record_names[i], config_callback, (void *)this);
345 if (status != REC_ERR_OKAY) {
346 Warning("couldn't register variable '%s', is %s up to date?", config_record_names[i], ts::filename::RECORDS);
347 }
348 no_registrations_failed = no_registrations_failed && (status == REC_ERR_OKAY);
349 }
350
351 if (no_registrations_failed == false) {
352 Warning("couldn't setup all body_factory callbacks, disabling body_factory");
353 } else {
354 Debug("body_factory", "all callbacks established successfully");
355 callbacks_established = true;
356 reconfigure();
357 }
358 }
359
~HttpBodyFactory()360 HttpBodyFactory::~HttpBodyFactory()
361 {
362 // FIX: need to implement destructor
363 table_of_sets.reset(nullptr);
364 }
365
366 // LOCKING: must be called with lock taken
367 char *
fabricate(StrList * acpt_language_list,StrList * acpt_charset_list,const char * type,HttpTransact::State * context,int64_t * buffer_length_return,const char ** content_language_return,const char ** content_charset_return,const char ** set_return)368 HttpBodyFactory::fabricate(StrList *acpt_language_list, StrList *acpt_charset_list, const char *type, HttpTransact::State *context,
369 int64_t *buffer_length_return, const char **content_language_return, const char **content_charset_return,
370 const char **set_return)
371 {
372 char *buffer;
373 const char *pType = context->txn_conf->body_factory_template_base;
374 const char *set;
375 char template_base[PATH_NAME_MAX];
376
377 if (set_return) {
378 *set_return = "???";
379 }
380 *content_language_return = nullptr;
381 *content_charset_return = nullptr;
382
383 Debug("body_factory", "calling fabricate(type '%s')", type);
384 *buffer_length_return = 0;
385
386 // if error body suppressed, return NULL
387 if (is_response_suppressed(context)) {
388 Debug("body_factory", " error suppression enabled, returning NULL template");
389 return nullptr;
390 }
391 // if custom error pages are disabled, return NULL
392 if (!enable_customizations) {
393 Debug("body_factory", " customization disabled, returning NULL template");
394 return nullptr;
395 }
396
397 // what set should we use (language target if enable_customizations == 2)
398 if (enable_customizations == 2) {
399 set = determine_set_by_language(acpt_language_list, acpt_charset_list);
400 } else if (enable_customizations == 3) {
401 set = determine_set_by_host(context);
402 } else if (is_response_body_precluded(context->http_return_code)) {
403 return nullptr;
404 } else {
405 set = "default";
406 }
407 if (set_return) {
408 *set_return = set;
409 }
410
411 HttpBodyTemplate *t = nullptr;
412 HttpBodySet *body_set = nullptr;
413 if (pType != nullptr && 0 != *pType && 0 != strncmp(pType, "NONE", 4)) {
414 sprintf(template_base, "%s_%s", pType, type);
415 t = find_template(set, template_base, &body_set);
416 // Check for default alternate.
417 if (t == nullptr) {
418 sprintf(template_base, "%s_default", pType);
419 t = find_template(set, template_base, &body_set);
420 }
421 }
422
423 // Check for base customizations if specializations didn't work.
424 if (t == nullptr) {
425 if (is_response_body_precluded(context->http_return_code)) {
426 return nullptr;
427 }
428 t = find_template(set, type, &body_set); // this executes if the template_base is wrong and doesn't exist
429 }
430
431 if (t == nullptr) {
432 Debug("body_factory", " can't find template, returning NULL template");
433 return nullptr;
434 }
435
436 *content_language_return = body_set->content_language;
437 *content_charset_return = body_set->content_charset;
438
439 // build the custom error page
440 buffer = t->build_instantiated_buffer(context, buffer_length_return);
441 return buffer;
442 }
443
444 // LOCKING: must be called with lock taken
445 const char *
determine_set_by_host(HttpTransact::State * context)446 HttpBodyFactory::determine_set_by_host(HttpTransact::State *context)
447 {
448 const char *set;
449 int host_len = context->hh_info.host_len;
450 char host_buffer[host_len + 1];
451 strncpy(host_buffer, context->hh_info.request_host, host_len);
452 host_buffer[host_len] = '\0';
453 if (auto it = table_of_sets->find(host_buffer); it != table_of_sets->end()) {
454 set = it->first.c_str();
455 } else {
456 set = "default";
457 }
458 return set;
459 }
460
461 const char *
determine_set_by_language(std::unique_ptr<BodySetTable> & table_of_sets,StrList * acpt_language_list,StrList * acpt_charset_list,float * Q_best_ptr,int * La_best_ptr,int * Lc_best_ptr,int * I_best_ptr)462 HttpBodyFactory::determine_set_by_language(std::unique_ptr<BodySetTable> &table_of_sets, StrList *acpt_language_list,
463 StrList *acpt_charset_list, float *Q_best_ptr, int *La_best_ptr, int *Lc_best_ptr,
464 int *I_best_ptr)
465 {
466 float Q, Ql, Qc, Q_best;
467 int I, Idummy, I_best;
468 int La, Lc, La_best, Lc_best;
469 int is_the_default_set;
470 const char *set_best;
471
472 set_best = "default";
473 Q_best = 0.00001;
474 La_best = 0;
475 Lc_best = INT_MAX;
476 I_best = INT_MAX;
477
478 Debug("body_factory_determine_set", " INITIAL: [ set_best='%s', Q=%g, La=%d, Lc=%d, I=%d ]", set_best, Q_best, La_best, Lc_best,
479 I_best);
480
481 // FIX: eliminate this special case (which doesn't work anyway), by properly
482 // handling empty lists and empty pieces in match_accept_XXX
483
484 // if no Accept-Language or Accept-Charset, just return default
485 if ((acpt_language_list->count == 0) && (acpt_charset_list->count == 0)) {
486 Q_best = 1;
487 Debug("body_factory_determine_set", " no constraints => returning '%s'", set_best);
488 goto done;
489 }
490
491 if (table_of_sets) {
492 ///////////////////////////////////////////
493 // loop over set->body-types hash table //
494 ///////////////////////////////////////////
495 for (const auto &it : *table_of_sets.get()) {
496 const char *set_name = it.first.c_str();
497 HttpBodySetRawData *body_set = it.second;
498
499 if ((it.first.empty()) || (body_set->table_of_pages == nullptr)) {
500 continue;
501 }
502
503 //////////////////////////////////////////////////////////////////////
504 // Take this error page language and match it against the //
505 // Accept-Language string passed in, to evaluate the match //
506 // quality. Disable wildcard processing so we use "default" //
507 // if no set explicitly matches. We also get back the index //
508 // of the match and the length of the match. //
509 // //
510 // We optimize the match in a couple of ways: //
511 // (a) if Q is better ==> wins, else if tie, //
512 // (b) if accept tag length La is bigger ==> wins, else if tie, //
513 // (c) if content tag length Lc is smaller ==> wins, else if tie, //
514 // (d) if index position I is smaller ==> wins //
515 //////////////////////////////////////////////////////////////////////
516
517 is_the_default_set = (strcmp(set_name, "default") == 0);
518
519 Debug("body_factory_determine_set", " --- SET: %-8s (Content-Language '%s', Content-Charset '%s')", set_name,
520 body_set->content_language, body_set->content_charset);
521
522 // if no Accept-Language hdr at all, treat as a wildcard that
523 // slightly prefers "default".
524 if (acpt_language_list->count == 0) {
525 Ql = (is_the_default_set ? 1.0001 : 1.000);
526 La = 0;
527 Lc = INT_MAX;
528 I = 1;
529 Debug("body_factory_determine_set", " SET: [%-8s] A-L not present => [ Ql=%g, La=%d, Lc=%d, I=%d ]", set_name, Ql, La,
530 Lc, I);
531 } else {
532 Lc = strlen(body_set->content_language);
533 Ql = HttpCompat::match_accept_language(body_set->content_language, Lc, acpt_language_list, &La, &I, true);
534 Debug("body_factory_determine_set", " SET: [%-8s] A-L match value => [ Ql=%g, La=%d, Lc=%d, I=%d ]", set_name, Ql, La,
535 Lc, I);
536 }
537
538 /////////////////////////////////////////////////////////////
539 // Take this error page language and match it against the //
540 // Accept-Charset string passed in, to evaluate the match //
541 // quality. Disable wildcard processing so that only //
542 // explicit values match. (Many browsers will send along //
543 // "*" with all lists, and we really don't want to send //
544 // strange character sets for these people --- we'd rather //
545 // use a more portable "default" set. The index value we //
546 // get back isn't used, because it's a little hard to know //
547 // how to tradeoff language indices vs. charset indices. //
548 // If someone cares, we could surely work charset indices //
549 // into the sorting computation below. //
550 /////////////////////////////////////////////////////////////
551
552 // if no Accept-Charset hdr at all, treat as a wildcard that
553 // slightly prefers "default".
554 if (acpt_charset_list->count == 0) {
555 Qc = (is_the_default_set ? 1.0001 : 1.000);
556 Idummy = 1;
557 Debug("body_factory_determine_set", " SET: [%-8s] A-C not present => [ Qc=%g ]", set_name, Qc);
558 } else {
559 Qc = HttpCompat::match_accept_charset(body_set->content_charset, strlen(body_set->content_charset), acpt_charset_list,
560 &Idummy, true);
561 Debug("body_factory_determine_set", " SET: [%-8s] A-C match value => [ Qc=%g ]", set_name, Qc);
562 }
563
564 /////////////////////////////////////////////////////////////////
565 // We get back the Q value, the matching field length, and the //
566 // matching field index. We sort by largest Q value, but if //
567 // there is a Q tie, we sub sort on longer matching length, //
568 // and if there is a tie on Q and L, we sub sort on position //
569 // index, preferring values earlier in Accept-Language list. //
570 /////////////////////////////////////////////////////////////////
571
572 Q = std::min(Ql, Qc);
573
574 //////////////////////////////////////////////////////////
575 // normally the Q for default pages should be slightly //
576 // less than for normal pages, but default pages should //
577 // always match to a slight level, in case everything //
578 // else doesn't match (matches with Q=0). //
579 //////////////////////////////////////////////////////////
580
581 if (is_the_default_set) {
582 Q = Q + -0.00005;
583 if (Q < 0.00001) {
584 Q = 0.00001;
585 }
586 }
587
588 Debug("body_factory_determine_set", " NEW: [ set='%s', Q=%g, La=%d, Lc=%d, I=%d ]", set_name, Q, La, Lc, I);
589 Debug("body_factory_determine_set", " OLD: [ set='%s', Q=%g, La=%d, Lc=%d, I=%d ]", set_best, Q_best, La_best, Lc_best,
590 I_best);
591
592 if (((Q > Q_best)) || ((Q == Q_best) && (La > La_best)) || ((Q == Q_best) && (La == La_best) && (Lc < Lc_best)) ||
593 ((Q == Q_best) && (La == La_best) && (Lc == Lc_best) && (I < I_best))) {
594 Q_best = Q;
595 La_best = La;
596 Lc_best = Lc;
597 I_best = I;
598 set_best = set_name;
599
600 Debug("body_factory_determine_set", " WINNER: [ set_best='%s', Q=%g, La=%d, Lc=%d, I=%d ]", set_best, Q_best, La_best,
601 Lc_best, I_best);
602 } else {
603 Debug("body_factory_determine_set", " LOSER: [ set_best='%s', Q=%g, La=%d, Lc=%d, I=%d ]", set_best, Q_best, La_best,
604 Lc_best, I_best);
605 }
606 }
607 }
608
609 done:
610
611 *Q_best_ptr = Q_best;
612 *La_best_ptr = La_best;
613 *Lc_best_ptr = Lc_best;
614 *I_best_ptr = I_best;
615 return set_best;
616 }
617
618 // LOCKING: must be called with lock taken
619 const char *
determine_set_by_language(StrList * acpt_language_list,StrList * acpt_charset_list)620 HttpBodyFactory::determine_set_by_language(StrList *acpt_language_list, StrList *acpt_charset_list)
621 {
622 float Q_best;
623 const char *set_best;
624 int La_best, Lc_best, I_best;
625
626 set_best = determine_set_by_language(table_of_sets, acpt_language_list, acpt_charset_list, &Q_best, &La_best, &Lc_best, &I_best);
627
628 return set_best;
629 }
630
631 // LOCKING: must be called with lock taken
632 HttpBodyTemplate *
find_template(const char * set,const char * type,HttpBodySet ** body_set_return)633 HttpBodyFactory::find_template(const char *set, const char *type, HttpBodySet **body_set_return)
634 {
635 Debug("body_factory", "calling find_template(%s,%s)", set, type);
636
637 *body_set_return = nullptr;
638
639 if (table_of_sets == nullptr || !set || !type) {
640 return nullptr;
641 }
642 if (auto it = table_of_sets->find(set); it != table_of_sets->end()) {
643 HttpBodySet *body_set = static_cast<HttpBodySet *>(it->second);
644 if (body_set->table_of_pages == nullptr) {
645 return nullptr;
646 }
647
648 if (auto it_page = body_set->table_of_pages->find(type); it_page != body_set->table_of_pages->end()) {
649 HttpBodyTemplate *t = it_page->second;
650 if ((t == nullptr) || (!t->is_sane())) {
651 return nullptr;
652 }
653 *body_set_return = body_set;
654
655 Debug("body_factory", "find_template(%s,%s) -> (file %s, length %" PRId64 ", lang '%s', charset '%s')", set, type,
656 t->template_pathname, t->byte_count, body_set->content_language, body_set->content_charset);
657
658 return t;
659 }
660 }
661 Debug("body_factory", "find_template(%s,%s) -> NULL", set, type);
662
663 return nullptr;
664 }
665
666 // LOCKING: must be called with lock taken
667 bool
is_response_suppressed(HttpTransact::State * context)668 HttpBodyFactory::is_response_suppressed(HttpTransact::State *context)
669 {
670 // Since a tunnel may not always be an SSL connection,
671 // we may want to return an error message.
672 // Even if it's an SSL connection, it won't cause any harm
673 // as the connection is going to be closed anyway.
674 /*
675 if (context->client_info.port_attribute == SERVER_PORT_BLIND_TUNNEL) {
676 // Blind SSL tunnels always suppress error messages
677 return true;
678 } else
679 */
680 if (response_suppression_mode == 0) {
681 return false;
682 } else if (response_suppression_mode == 1) {
683 return true;
684 } else if (response_suppression_mode == 2) {
685 return context->request_data.internal_txn;
686 } else {
687 return false;
688 }
689 }
690
691 // LOCKING: must be called with lock taken
692 void
nuke_template_tables()693 HttpBodyFactory::nuke_template_tables()
694 {
695 if (table_of_sets) {
696 Debug("body_factory", "deleting pre-existing template tables");
697 } else {
698 Debug("body_factory", "no pre-existing template tables");
699 }
700 if (table_of_sets) {
701 ///////////////////////////////////////////
702 // loop over set->body-types hash table //
703 ///////////////////////////////////////////
704 for (const auto &it : *table_of_sets.get()) {
705 HttpBodySet *body_set = static_cast<HttpBodySet *>(it.second);
706 ink_assert(body_set->is_sane());
707 if (body_set->table_of_pages) {
708 ///////////////////////////////////////////
709 // loop over body-types->body hash table //
710 ///////////////////////////////////////////
711 for (const auto &it_page : *body_set->table_of_pages.get()) {
712 delete it_page.second;
713 }
714 body_set->table_of_pages.reset(nullptr);
715 }
716 delete body_set;
717 }
718 table_of_sets.reset(nullptr);
719 }
720 }
721
722 // LOCKING: must be called with lock taken
723 std::unique_ptr<HttpBodyFactory::BodySetTable>
load_sets_from_directory(char * set_dir)724 HttpBodyFactory::load_sets_from_directory(char *set_dir)
725 {
726 DIR *dir;
727 struct dirent *dirEntry;
728 std::unique_ptr<HttpBodyFactory::BodySetTable> new_table_of_sets;
729
730 if (set_dir == nullptr) {
731 return nullptr;
732 }
733
734 Debug("body_factory", "load_sets_from_directory(%s)", set_dir);
735
736 //////////////////////////////////////////////////
737 // try to open the requested template directory //
738 //////////////////////////////////////////////////
739
740 dir = opendir(set_dir);
741 if (dir == nullptr) {
742 Warning("can't open response template directory '%s' (%s)", set_dir, (strerror(errno) ? strerror(errno) : "unknown reason"));
743 Warning("no response templates --- using default error pages");
744 return nullptr;
745 }
746
747 new_table_of_sets.reset(new HttpBodyFactory::BodySetTable);
748
749 //////////////////////////////////////////
750 // loop over each language subdirectory //
751 //////////////////////////////////////////
752
753 while ((dirEntry = readdir(dir))) {
754 int status;
755 struct stat stat_buf;
756 char subdir[MAXPATHLEN + 1];
757
758 //////////////////////////////////////////////////////
759 // ensure a subdirectory, and not starting with '.' //
760 //////////////////////////////////////////////////////
761
762 if ((dirEntry->d_name)[0] == '.') {
763 continue;
764 }
765
766 ink_filepath_make(subdir, sizeof(subdir), set_dir, dirEntry->d_name);
767 status = stat(subdir, &stat_buf);
768 if (status != 0) {
769 continue; // can't stat
770 }
771
772 if (!S_ISDIR(stat_buf.st_mode)) {
773 continue; // not a dir
774 }
775
776 ///////////////////////////////////////////////////////////
777 // at this point, 'subdir' might be a valid template dir //
778 ///////////////////////////////////////////////////////////
779
780 HttpBodySet *body_set = load_body_set_from_directory(dirEntry->d_name, subdir);
781 if (body_set != nullptr) {
782 Debug("body_factory", " %s -> %p", dirEntry->d_name, body_set);
783 new_table_of_sets->emplace(dirEntry->d_name, body_set);
784 }
785 }
786
787 closedir(dir);
788
789 return new_table_of_sets;
790 }
791
792 // LOCKING: must be called with lock taken
793 HttpBodySet *
load_body_set_from_directory(char * set_name,char * tmpl_dir)794 HttpBodyFactory::load_body_set_from_directory(char *set_name, char *tmpl_dir)
795 {
796 DIR *dir;
797 int status;
798 struct stat stat_buf;
799 struct dirent *dirEntry;
800 char path[MAXPATHLEN + 1];
801 static const char BASED_DEFAULT[] = "_default";
802
803 ////////////////////////////////////////////////
804 // ensure we can open tmpl_dir as a directory //
805 ////////////////////////////////////////////////
806
807 Debug("body_factory", " load_body_set_from_directory(%s)", tmpl_dir);
808 dir = opendir(tmpl_dir);
809 if (dir == nullptr) {
810 return nullptr;
811 }
812
813 /////////////////////////////////////////////
814 // ensure a .body_factory_info file exists //
815 /////////////////////////////////////////////
816
817 ink_filepath_make(path, sizeof(path), tmpl_dir, ".body_factory_info");
818 status = stat(path, &stat_buf);
819 if ((status < 0) || !S_ISREG(stat_buf.st_mode)) {
820 Warning("Missing .body_factory_info in %s. Not loading body_factory templates", tmpl_dir);
821 closedir(dir);
822 return nullptr;
823 }
824 Debug("body_factory", " found '%s'", path);
825
826 /////////////////////////////////////////////////////////////////
827 // create body set, and loop over template files, loading them //
828 /////////////////////////////////////////////////////////////////
829
830 HttpBodySet *body_set = new HttpBodySet;
831 body_set->init(set_name, tmpl_dir);
832
833 Debug("body_factory", " body_set = %p (set_name '%s', lang '%s', charset '%s')", body_set, body_set->set_name,
834 body_set->content_language, body_set->content_charset);
835
836 while ((dirEntry = readdir(dir))) {
837 HttpBodyTemplate *tmpl;
838 size_t d_len = strlen(dirEntry->d_name);
839
840 ///////////////////////////////////////////////////////////////
841 // all template files must have a file name of the form //
842 // - <type>#<subtype> //
843 // - <base>_<type>#<subtype> //
844 // - <base>_default [based default] //
845 // - default [global default] //
846 ///////////////////////////////////////////////////////////////
847
848 if (!(nullptr != strchr(dirEntry->d_name, '#') || (0 == strcmp(dirEntry->d_name, "default")) ||
849 (d_len >= sizeof(BASED_DEFAULT) && 0 == strcmp(dirEntry->d_name + d_len - (sizeof(BASED_DEFAULT) - 1), BASED_DEFAULT)))) {
850 continue;
851 }
852
853 snprintf(path, sizeof(path), "%s/%s", tmpl_dir, dirEntry->d_name);
854 status = stat(path, &stat_buf);
855 if (status != 0) {
856 continue; // can't stat
857 }
858
859 if (!S_ISREG(stat_buf.st_mode)) {
860 continue; // not a file
861 }
862
863 ////////////////////////////////
864 // read in this template file //
865 ////////////////////////////////
866
867 tmpl = new HttpBodyTemplate();
868 if (!tmpl->load_from_file(tmpl_dir, dirEntry->d_name)) {
869 delete tmpl;
870 } else {
871 Debug("body_factory", " %s -> %p", dirEntry->d_name, tmpl);
872 body_set->set_template_by_name(dirEntry->d_name, tmpl);
873 }
874 }
875
876 closedir(dir);
877 return body_set;
878 }
879
880 ////////////////////////////////////////////////////////////////////////
881 //
882 // class HttpBodySet
883 //
884 ////////////////////////////////////////////////////////////////////////
885
HttpBodySet()886 HttpBodySet::HttpBodySet()
887 {
888 magic = HTTP_BODY_SET_MAGIC;
889
890 set_name = nullptr;
891 content_language = nullptr;
892 content_charset = nullptr;
893
894 table_of_pages = nullptr;
895 }
896
~HttpBodySet()897 HttpBodySet::~HttpBodySet()
898 {
899 ats_free(set_name);
900 ats_free(content_language);
901 ats_free(content_charset);
902 table_of_pages.reset(nullptr);
903 }
904
905 int
init(char * set,char * dir)906 HttpBodySet::init(char *set, char *dir)
907 {
908 int fd, lineno, lines_added = 0;
909 char info_path[MAXPATHLEN];
910
911 char buffer[1024], name[1025], value[1024];
912
913 ink_filepath_make(info_path, sizeof(info_path), dir, ".body_factory_info");
914 fd = open(info_path, O_RDONLY);
915 if (fd < 0) {
916 return -1;
917 }
918
919 this->set_name = ats_strdup(set);
920 this->table_of_pages.reset(new TemplateTable);
921
922 lineno = 0;
923
924 while (true) {
925 char *name_s, *name_e, *value_s, *value_e, *hash;
926
927 ++lineno;
928 int bytes = ink_file_fd_readline(fd, sizeof(buffer), buffer);
929 if (bytes <= 0) {
930 break;
931 }
932
933 ///////////////////////////////////////////////
934 // chop anything on and after first '#' sign //
935 ///////////////////////////////////////////////
936
937 hash = index(buffer, '#');
938 if (hash) {
939 *hash = '\0';
940
941 ////////////////////////////////
942 // find start and end of name //
943 ////////////////////////////////
944 }
945 name_s = buffer;
946 while (*name_s && ParseRules::is_wslfcr(*name_s)) {
947 ++name_s;
948 }
949
950 name_e = name_s;
951 while (*name_e && ParseRules::is_http_field_name(*name_e)) {
952 ++name_e;
953 }
954
955 if (name_s == name_e) {
956 continue; // blank line
957 }
958
959 /////////////////////////////////
960 // find start and end of value //
961 /////////////////////////////////
962
963 value_s = name_e;
964 while (*value_s && (ParseRules::is_wslfcr(*value_s))) {
965 ++value_s;
966 }
967 if (*value_s != ':') {
968 Warning("ignoring invalid body factory info line #%d in %s", lineno, info_path);
969 continue;
970 }
971 ++value_s; // skip the colon
972 while (*value_s && (ParseRules::is_wslfcr(*value_s))) {
973 ++value_s;
974 }
975 value_e = buffer + strlen(buffer) - 1;
976 while ((value_e > value_s) && ParseRules::is_wslfcr(*(value_e - 1))) {
977 --value_e;
978 }
979
980 /////////////////////////////////
981 // insert line into hash table //
982 /////////////////////////////////
983
984 memcpy(name, name_s, name_e - name_s);
985 name[name_e - name_s] = '\0';
986
987 memcpy(value, value_s, value_e - value_s);
988 value[value_e - value_s] = '\0';
989
990 //////////////////////////////////////////////////
991 // so far, we only support 2 pieces of metadata //
992 //////////////////////////////////////////////////
993
994 if (strcasecmp(name, "Content-Language") == 0) {
995 ats_free(this->content_language);
996 this->content_language = ats_strdup(value);
997 } else if (strcasecmp(name, "Content-Charset") == 0) {
998 ats_free(this->content_charset);
999 this->content_charset = ats_strdup(value);
1000 }
1001 }
1002
1003 ////////////////////////////////////////////////////
1004 // fill in default language & charset, if not set //
1005 ////////////////////////////////////////////////////
1006
1007 if (!this->content_language) {
1008 if (strcmp(set, "default") == 0) {
1009 this->content_language = ats_strdup("en");
1010 } else {
1011 this->content_language = ats_strdup(set);
1012 }
1013 }
1014 if (!this->content_charset) {
1015 this->content_charset = ats_strdup("utf-8");
1016 }
1017
1018 close(fd);
1019 return lines_added;
1020 }
1021
1022 HttpBodyTemplate *
get_template_by_name(const char * name)1023 HttpBodySet::get_template_by_name(const char *name)
1024 {
1025 Debug("body_factory", " calling get_template_by_name(%s)", name);
1026
1027 if (table_of_pages == nullptr) {
1028 return nullptr;
1029 }
1030
1031 if (auto it = table_of_pages->find(name); it != table_of_pages->end()) {
1032 HttpBodyTemplate *t = it->second;
1033 if ((t == nullptr) || (!t->is_sane())) {
1034 return nullptr;
1035 }
1036 Debug("body_factory", " get_template_by_name(%s) -> (file %s, length %" PRId64 ")", name, t->template_pathname,
1037 t->byte_count);
1038 return t;
1039 }
1040 Debug("body_factory", " get_template_by_name(%s) -> NULL", name);
1041 return nullptr;
1042 }
1043
1044 void
set_template_by_name(const char * name,HttpBodyTemplate * t)1045 HttpBodySet::set_template_by_name(const char *name, HttpBodyTemplate *t)
1046 {
1047 if (name) {
1048 table_of_pages->emplace(name, t);
1049 }
1050 }
1051
1052 ////////////////////////////////////////////////////////////////////////
1053 //
1054 // class HttpBodyTemplate
1055 //
1056 ////////////////////////////////////////////////////////////////////////
1057
HttpBodyTemplate()1058 HttpBodyTemplate::HttpBodyTemplate()
1059 {
1060 magic = HTTP_BODY_TEMPLATE_MAGIC;
1061 byte_count = 0;
1062 template_buffer = nullptr;
1063 template_pathname = nullptr;
1064 }
1065
~HttpBodyTemplate()1066 HttpBodyTemplate::~HttpBodyTemplate()
1067 {
1068 reset();
1069 }
1070
1071 void
reset()1072 HttpBodyTemplate::reset()
1073 {
1074 ats_free(template_buffer);
1075 template_buffer = nullptr;
1076 byte_count = 0;
1077 ats_free(template_pathname);
1078 }
1079
1080 int
load_from_file(char * dir,char * file)1081 HttpBodyTemplate::load_from_file(char *dir, char *file)
1082 {
1083 int fd, status;
1084 int64_t bytes_read;
1085 struct stat stat_buf;
1086 char path[MAXPATHLEN + 1];
1087 char *new_template_buffer;
1088 int64_t new_byte_count;
1089
1090 ////////////////////////////////////
1091 // ensure this is actually a file //
1092 ////////////////////////////////////
1093
1094 snprintf(path, sizeof(path), "%s/%s", dir, file);
1095 // coverity[fs_check_call]
1096 status = stat(path, &stat_buf);
1097 if (status != 0) {
1098 return 0;
1099 }
1100 if (!S_ISREG(stat_buf.st_mode)) {
1101 return 0;
1102 }
1103
1104 ///////////////////
1105 // open the file //
1106 ///////////////////
1107
1108 // coverity[toctou]
1109 fd = open(path, O_RDONLY);
1110 if (fd < 0) {
1111 return 0;
1112 }
1113
1114 ////////////////////////////////////////
1115 // read in the template file contents //
1116 ////////////////////////////////////////
1117
1118 new_byte_count = stat_buf.st_size;
1119 new_template_buffer = static_cast<char *>(ats_malloc(new_byte_count + 1));
1120 bytes_read = read(fd, new_template_buffer, new_byte_count);
1121 new_template_buffer[new_byte_count] = '\0';
1122 close(fd);
1123
1124 ///////////////////////////
1125 // check for read errors //
1126 ///////////////////////////
1127
1128 if (bytes_read != new_byte_count) {
1129 Warning("reading template file '%s', got %" PRId64 " bytes instead of %" PRId64 " (%s)", path, bytes_read, new_byte_count,
1130 (strerror(errno) ? strerror(errno) : "unknown error"));
1131 ats_free(new_template_buffer);
1132 return 0;
1133 }
1134
1135 Debug("body_factory", " read %" PRId64 " bytes from '%s'", new_byte_count, path);
1136
1137 /////////////////////////////////
1138 // actually commit the changes //
1139 /////////////////////////////////
1140
1141 reset();
1142 template_buffer = new_template_buffer;
1143 byte_count = new_byte_count;
1144 template_pathname = ats_strdup(path);
1145
1146 return 1;
1147 }
1148
1149 char *
build_instantiated_buffer(HttpTransact::State * context,int64_t * buflen_return)1150 HttpBodyTemplate::build_instantiated_buffer(HttpTransact::State *context, int64_t *buflen_return)
1151 {
1152 char *buffer = nullptr;
1153
1154 Debug("body_factory_instantiation", " before instantiation: [%s]", template_buffer);
1155
1156 LogAccess la(context->state_machine);
1157
1158 buffer = resolve_logfield_string(&la, template_buffer);
1159
1160 *buflen_return = ((buffer == nullptr) ? 0 : strlen(buffer));
1161 Debug("body_factory_instantiation", " after instantiation: [%s]", buffer);
1162 Debug("body_factory", " returning %" PRId64 " byte instantiated buffer", *buflen_return);
1163
1164 return buffer;
1165 }
1166