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