blob: 584398f81cdb258e99bab2146a1e84c786d4a10a [file] [log] [blame]
/** @file
A brief file description
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/****************************************************************************
HttpBodyFactory.cc
****************************************************************************/
#include "HttpBodyFactory.h"
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "HttpMessageBody.h"
#include "URL.h"
#include <logging/Log.h>
#include <logging/LogAccess.h>
#include <logging/LogAccessHttp.h>
#include "HttpCompat.h"
#include "inktomi++.h"
//////////////////////////////////////////////////////////////////////
// The HttpBodyFactory creates HTTP response page bodies, supported //
// configurable customization and language-targeting. //
// //
// The body factory can be reconfigured dynamically by a manager //
// callback, so locking is required. The callback takes a lock, //
// and the user entry points take a lock. These locks may limit //
// the speed of error page generation. //
//////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
//
// User-Callable APIs --- locks will be taken internally
//
////////////////////////////////////////////////////////////////////////
char *
HttpBodyFactory::fabricate_with_old_api(char *type, HttpTransact::State * context,
int max_buffer_length, int *resulting_buffer_length,
char content_language_out[256],
char content_type_out[256],
HTTPStatus status_code, char *reason_or_null, char *format, va_list ap)
{
char *buffer = NULL;
const char *lang_ptr = NULL;
const char *charset_ptr = NULL;
char url[1024];
const char *set = NULL;
bool found_requested_template = false;
bool plain_flag = false;
lock();
*resulting_buffer_length = 0;
ink_strncpy(content_language_out, "en", sizeof(content_language_out));
ink_strncpy(content_type_out, "text/html", sizeof(content_type_out));
///////////////////////////////////////////////////////////////////
// if logging turned on, buffer up the URL string for simplicity //
///////////////////////////////////////////////////////////////////
if (enable_logging && !context->traffic_net_req) {
url[0] = '\0';
URL *u = context->hdr_info.client_request.url_get();
if (u->valid()) { /* if url exists, copy the string into buffer */
size_t i;
char *s = u->string_get(&context->arena);
for (i = 0; (i < sizeof(url) - 1) && s[i]; i++)
url[i] = s[i];
url[i] = '\0';
if (s)
context->arena.str_free(s);
}
}
///////////////////////////////////////////////
// if suppressing this response, return NULL //
///////////////////////////////////////////////
if (is_response_suppressed(context)) {
if (enable_logging && !context->traffic_net_req) {
Log::error("BODY_FACTORY: suppressing '%s' response for url '%s'", type, url);
}
unlock();
return (NULL);
}
//////////////////////////////////////////////////////////////////////////////////
// if language-targeting activated, get client Accept-Language & Accept-Charset //
//////////////////////////////////////////////////////////////////////////////////
StrList acpt_language_list(false);
StrList acpt_charset_list(false);
if (enable_customizations == 2) {
context->hdr_info.client_request.value_get_comma_list(MIME_FIELD_ACCEPT_LANGUAGE, MIME_LEN_ACCEPT_LANGUAGE,
&acpt_language_list);
context->hdr_info.client_request.value_get_comma_list(MIME_FIELD_ACCEPT_CHARSET, MIME_LEN_ACCEPT_CHARSET,
&acpt_charset_list);
}
///////////////////////////////////////////
// check if we don't need to format body //
///////////////////////////////////////////
if (context->return_xbuf_plain && format) {
int l = ink_bvsprintf(NULL, format, ap);
if (l < max_buffer_length) {
buffer = (char *) xmalloc(l + 1);
*resulting_buffer_length = ink_bvsprintf(buffer, format, ap);
plain_flag = true;
}
}
/////////////////////////////////////////////////////////
// try to fabricate the desired type of error response //
/////////////////////////////////////////////////////////
if (buffer == NULL) {
buffer = fabricate(&acpt_language_list, &acpt_charset_list,
type, context, resulting_buffer_length, &lang_ptr, &charset_ptr, &set);
found_requested_template = (buffer != NULL);
}
/////////////////////////////////////////////////////////////
// if failed, try to fabricate the default custom response //
/////////////////////////////////////////////////////////////
if (buffer == NULL) {
buffer = fabricate(&acpt_language_list, &acpt_charset_list,
"default", context, resulting_buffer_length, &lang_ptr, &charset_ptr, &set);
}
///////////////////////////////////
// enforce the max buffer length //
///////////////////////////////////
if (buffer && (*resulting_buffer_length > max_buffer_length)) {
if (enable_logging && !context->traffic_net_req) {
Log::error(("BODY_FACTORY: template '%s/%s' consumed %d bytes, "
"exceeding %d byte limit, using internal default"),
set, type, *resulting_buffer_length, max_buffer_length);
}
*resulting_buffer_length = 0;
buffer = (char *) xfree_null(buffer);
}
/////////////////////////////////////////////////////////////////////
// handle return of instantiated template or internal default, and //
// generate the content language and content type return values //
/////////////////////////////////////////////////////////////////////
if (buffer) // got an instantiated template
{
if (!plain_flag) {
snprintf(content_language_out, sizeof(content_language_out), "%s", lang_ptr);
snprintf(content_type_out, sizeof(content_type_out), "text/html; charset=%s", charset_ptr);
}
if (enable_logging && !context->traffic_net_req) {
if (found_requested_template) // got exact template
{
Log::error(("BODY_FACTORY: using custom template "
"'%s/%s' for url '%s' (language '%s', charset '%s')"), set, type, url, lang_ptr, charset_ptr);
} else // got default template
{
Log::error(("BODY_FACTORY: can't find custom template "
"'%s/%s', using '%s/%s' for url '%s' (language '%s', charset '%s')"),
set, type, set, "default", url, lang_ptr, charset_ptr);
}
}
} else // no template, using old style body
{
buffer = HttpMessageBody::MakeErrorBodyVA(max_buffer_length, resulting_buffer_length,
context->http_config_param, status_code, reason_or_null, format, ap);
if (enable_logging && !context->traffic_net_req) {
if (enable_customizations) // we wanted a template
{
Log::error(("BODY_FACTORY: customization enabled "
"but can't find templates '%s' or '%s', using hardcoded default body for url '%s'"),
type, "default", url);
} else // we wanted an old-style body
{
Log::error(("BODY_FACTORY: using hardcoded default '%s' body for url '%s'"), type, url);
}
}
}
unlock();
return (buffer);
}
void
HttpBodyFactory::dump_template_tables(FILE * fp)
{
RawHashTable *h1, *h2;
RawHashTable_Key k1, k2;
RawHashTable_Value v1, v2;
RawHashTable_Binding *b1, *b2;
RawHashTable_IteratorState i1, i2;
HttpBodySet *body_set;
lock();
h1 = table_of_sets;
if (h1 != NULL) {
///////////////////////////////////////////
// loop over set->body-types hash table //
///////////////////////////////////////////
for (b1 = h1->firstBinding(&i1); b1 != NULL; b1 = h1->nextBinding(&i1)) {
k1 = table_of_sets->getKeyFromBinding(b1);
v1 = table_of_sets->getValueFromBinding(b1);
body_set = (HttpBodySet *) v1;
if (body_set != NULL) {
fprintf(fp, "set %s: name '%s', lang '%s', charset '%s'\n",
k1, body_set->set_name, body_set->content_language, body_set->content_charset);
///////////////////////////////////////////
// loop over body-types->body hash table //
///////////////////////////////////////////
ink_assert(body_set->is_sane());
h2 = body_set->table_of_pages;
for (b2 = h2->firstBinding(&i2); b2 != NULL; b2 = h2->nextBinding(&i2)) {
k2 = table_of_sets->getKeyFromBinding(b2);
v2 = table_of_sets->getValueFromBinding(b2);
HttpBodyTemplate *t = (HttpBodyTemplate *) v2;
fprintf(fp, " %-30s: %d bytes\n", k2, t->byte_count);
}
}
}
}
unlock();
}
////////////////////////////////////////////////////////////////////////
//
// Configuration Change Callback
//
////////////////////////////////////////////////////////////////////////
static int
config_callback(const char *name, RecDataT data_type, RecData data, void *cookie)
{
HttpBodyFactory *body_factory = (HttpBodyFactory *) cookie;
body_factory->reconfigure();
return (0);
}
//#ifdef MODULARIZED
//void HttpBodyFactory::reconfigure(const char* name, RecDataT data_type,
// RecData data, void *cookie)
//#else
void
HttpBodyFactory::reconfigure()
//#endif
{
RecInt e;
RecString s;
bool all_found;
int rec_err;
struct stat st;
int err;
lock();
sanity_check();
if (!callbacks_established) {
unlock();
return;
} // callbacks not setup right
////////////////////////////////////////////
// extract relevant records.config values //
////////////////////////////////////////////
Debug("body_factory", "config variables changed, reconfiguring...");
all_found = true;
// enable_customizations if records.config set
rec_err = RecGetRecordInt("proxy.config.body_factory.enable_customizations", &e);
enable_customizations = ((rec_err == REC_ERR_OKAY) ? e : 0);
all_found = all_found && (rec_err == REC_ERR_OKAY);
Debug("body_factory", "enable_customizations = %d (found = %d)", enable_customizations, e);
rec_err = RecGetRecordInt("proxy.config.body_factory.enable_logging", &e);
enable_logging = ((rec_err == REC_ERR_OKAY) ? (e ? true : false) : false);
all_found = all_found && (rec_err == REC_ERR_OKAY);
Debug("body_factory", "enable_logging = %d (found = %d)", enable_logging, e);
rec_err = RecGetRecordInt("proxy.config.body_factory.response_suppression_mode", &e);
response_suppression_mode = ((rec_err == REC_ERR_OKAY) ? e : 0);
all_found = all_found && (rec_err == REC_ERR_OKAY);
Debug("body_factory", "response_suppression_mode = %d (found = %d)", response_suppression_mode, e);
rec_err = RecGetRecordString_Xmalloc("proxy.config.body_factory.template_sets_dir", &s);
all_found = all_found && (rec_err == REC_ERR_OKAY);
if (rec_err == REC_ERR_OKAY) {
if (directory_of_template_sets) {
xfree(directory_of_template_sets);
}
directory_of_template_sets = s;
if ((err = stat(directory_of_template_sets, &st)) < 0) {
if ((err = stat(system_config_directory, &st)) < 0) {
Warning("Unable to stat() directory '%s': %d %d, %s", system_config_directory, err, errno, strerror(errno));
Warning(" Please set 'proxy.config.body_factory.template_sets_dir' ");
} else {
xfree(directory_of_template_sets);
directory_of_template_sets = xstrdup(system_config_directory);
}
}
}
Debug("body_factory", "directory_of_template_sets = '%s' (found = %d)", directory_of_template_sets, e);
if (!all_found) {
Warning("config changed, but can't fetch all proxy.config.body_factory values");
}
/////////////////////////////////////////////
// clear out previous template hash tables //
/////////////////////////////////////////////
nuke_template_tables();
/////////////////////////////////////////////////////////////
// at this point, the body hash table is gone, so we start //
// building a new one, by scanning the template directory. //
/////////////////////////////////////////////////////////////
if (directory_of_template_sets)
table_of_sets = load_sets_from_directory(directory_of_template_sets);
unlock();
}
////////////////////////////////////////////////////////////////////////
//
// class HttpBodyFactory
//
////////////////////////////////////////////////////////////////////////
HttpBodyFactory::HttpBodyFactory()
{
int i;
bool status, no_registrations_failed;
////////////////////////////////////
// initialize first-time defaults //
////////////////////////////////////
magic = HTTP_BODY_FACTORY_MAGIC;
ink_mutex_init(&mutex, "HttpBodyFactory::lock");
table_of_sets = NULL;
directory_of_template_sets = NULL;
enable_customizations = 0;
enable_logging = true;
callbacks_established = false;
//////////////////////////////////////////////////////
// set up management configuration-change callbacks //
//////////////////////////////////////////////////////
static char *config_record_names[] = {
"proxy.config.body_factory.enable_customizations",
"proxy.config.body_factory.enable_logging",
"proxy.config.body_factory.template_sets_dir",
"proxy.config.body_factory.response_suppression_mode",
NULL
};
no_registrations_failed = true;
for (i = 0; config_record_names[i] != NULL; i++) {
status = HTTP_RegisterConfigUpdateFunc(config_record_names[i], config_callback, (void *) this);
if (status != REC_ERR_OKAY) {
Warning("couldn't register variable '%s', is records.config up to date?", config_record_names[i]);
}
no_registrations_failed = no_registrations_failed && (status == REC_ERR_OKAY);
}
if (no_registrations_failed == false) {
Warning("couldn't setup all body_factory callbacks, disabling body_factory");
} else {
Debug("body_factory", "all callbacks established successfully");
callbacks_established = true;
reconfigure();
}
}
HttpBodyFactory::~HttpBodyFactory()
{
// FIX: need to implement destructor
delete table_of_sets;
}
// LOCKING: must be called with lock taken
char *
HttpBodyFactory::fabricate(StrList * acpt_language_list,
StrList * acpt_charset_list,
char *type, HttpTransact::State * context,
int *buffer_length_return,
const char **content_language_return,
const char **content_charset_return, const char **set_return)
{
char *buffer;
const char *set;
HttpBodyTemplate *t;
HttpBodySet *body_set;
if (set_return)
*set_return = "???";
*content_language_return = NULL;
*content_charset_return = NULL;
Debug("body_factory", "calling fabricate(type '%s')", type);
*buffer_length_return = 0;
// if error body suppressed, return NULL
if (is_response_suppressed(context)) {
Debug("body_factory", " error suppression enabled, returning NULL template");
return (NULL);
}
// if custom error pages are disabled, return NULL
if (!enable_customizations) {
Debug("body_factory", " customization disabled, returning NULL template");
return (NULL);
}
// what set should we use (language target if enable_customizations == 2)
if (enable_customizations == 2)
set = determine_set_by_language(acpt_language_list, acpt_charset_list);
else
set = "default";
if (set_return)
*set_return = set;
// see if we have a custom error page template
t = find_template(set, type, &body_set);
if (t == NULL) {
Debug("body_factory", " can't find template, returning NULL template");
return (NULL);
}
*content_language_return = body_set->content_language;
*content_charset_return = body_set->content_charset;
// build the custom error page
buffer = t->build_instantiated_buffer(context, buffer_length_return);
return (buffer);
}
// LOCKING: must be called with lock taken
const char *
HttpBodyFactory::determine_set_by_language(StrList * acpt_language_list, StrList * acpt_charset_list)
{
float Q_best;
const char *set_best;
int La_best, Lc_best, I_best;
set_best = HttpCompat::determine_set_by_language(table_of_sets, acpt_language_list, acpt_charset_list,
&Q_best, &La_best, &Lc_best, &I_best);
return (set_best);
}
// LOCKING: must be called with lock taken
HttpBodyTemplate *
HttpBodyFactory::find_template(const char *set, const char *type, HttpBodySet ** body_set_return)
{
RawHashTable_Value v;
Debug("body_factory", "calling find_template(%s,%s)", set, type);
*body_set_return = NULL;
if (table_of_sets == NULL)
return (NULL);
if (table_of_sets->getValue((RawHashTable_Key) set, &v)) {
HttpBodySet *body_set = (HttpBodySet *) v;
RawHashTable *table_of_types = body_set->table_of_pages;
if (table_of_types == NULL)
return (NULL);
if (table_of_types->getValue((RawHashTable_Key) type, &v)) {
HttpBodyTemplate *t = (HttpBodyTemplate *) v;
if ((t == NULL) || (!t->is_sane()))
return (NULL);
*body_set_return = body_set;
Debug("body_factory", "find_template(%s,%s) -> (file %s, length %d, lang '%s', charset '%s')",
set, type, t->template_pathname, t->byte_count, body_set->content_language, body_set->content_charset);
return (t);
}
}
Debug("body_factory", "find_template(%s,%s) -> NULL", set, type);
return (NULL);
}
// LOCKING: must be called with lock taken
bool
HttpBodyFactory::is_response_suppressed(HttpTransact::State * context)
{
// Since a tunnel may not always be an SSL connection,
// we may want to return an error message.
// Even if it's an SSL connection, it won't cause any harm
// as the connection is going to be closed anyway.
/*
if (context->client_info.port_attribute == SERVER_PORT_BLIND_TUNNEL) {
// Blind SSL tunnels always supress error messages
return true;
} else
*/
if (response_suppression_mode == 0) {
return (false);
} else if (response_suppression_mode == 1) {
return (true);
} else if (response_suppression_mode == 2) {
if (context->req_flavor == HttpTransact::REQ_FLAVOR_INTERCEPTED)
return (true);
else
return (false);
} else {
return (false);
}
}
// LOCKING: must be called with lock taken
void
HttpBodyFactory::nuke_template_tables()
{
RawHashTable *h1, *h2;
RawHashTable_Value v1, v2;
RawHashTable_Binding *b1, *b2;
RawHashTable_IteratorState i1, i2;
HttpBodySet *body_set;
HttpBodyTemplate *hbt;
h1 = table_of_sets;
if (h1) {
Debug("body_factory", "deleting pre-existing template tables");
} else {
Debug("body_factory", "no pre-existing template tables");
}
if (h1 != NULL) {
///////////////////////////////////////////
// loop over set->body-types hash table //
///////////////////////////////////////////
for (b1 = h1->firstBinding(&i1); b1 != NULL; b1 = h1->nextBinding(&i1)) {
v1 = h1->getValueFromBinding(b1);
body_set = (HttpBodySet *) v1;
ink_assert(body_set->is_sane());
h2 = body_set->table_of_pages;
if (h2 != NULL) {
body_set->table_of_pages = NULL;
///////////////////////////////////////////
// loop over body-types->body hash table //
///////////////////////////////////////////
for (b2 = h2->firstBinding(&i2); b2 != NULL; b2 = h2->nextBinding(&i2)) {
v2 = h2->getValueFromBinding(b2);
if (v2) {
// need a cast here
hbt = (HttpBodyTemplate *) v2;
delete hbt;
}
}
delete h2;
}
delete body_set;
}
delete h1;
}
table_of_sets = NULL;
}
// LOCKING: must be called with lock taken
RawHashTable *
HttpBodyFactory::load_sets_from_directory(char *set_dir)
{
DIR *dir;
struct dirent *entry_buffer, *result;
RawHashTable *new_table_of_sets;
if (set_dir == NULL)
return (NULL);
Debug("body_factory", "load_sets_from_directory(%s)", set_dir);
//////////////////////////////////////////////////
// try to open the requested template directory //
//////////////////////////////////////////////////
dir = ink_opendir(set_dir);
if (dir == NULL) {
Warning("can't open response template directory '%s' (%s)",
set_dir, (strerror(errno) ? strerror(errno) : "unknown reason"));
Warning("no response templates --- using default error pages");
return (NULL);
}
new_table_of_sets = NEW(new RawHashTable(RawHashTable_KeyType_String));
entry_buffer = (struct dirent *)
xmalloc(sizeof(struct dirent) + MAXPATHLEN + 1);
//////////////////////////////////////////
// loop over each language subdirectory //
//////////////////////////////////////////
while ((ink_readdir_r(dir, entry_buffer, &result) == 0) && (result != NULL)) {
int status;
struct stat stat_buf;
char subdir[MAXPATHLEN + 1];
//////////////////////////////////////////////////////
// ensure a subdirectory, and not starting with '.' //
//////////////////////////////////////////////////////
if ((entry_buffer->d_name)[0] == '.')
continue;
ink_snprintf(subdir, sizeof(subdir), "%s" DIR_SEP "%s", set_dir, entry_buffer->d_name);
status = stat(subdir, &stat_buf);
if (status != 0)
continue; // can't stat
if (!S_ISDIR(stat_buf.st_mode))
continue; // not a dir
///////////////////////////////////////////////////////////
// at this point, 'subdir' might be a valid template dir //
///////////////////////////////////////////////////////////
HttpBodySet *body_set = load_body_set_from_directory(entry_buffer->d_name, subdir);
if (body_set != NULL) {
Debug("body_factory", " %s -> 0x%0X", entry_buffer->d_name, body_set);
new_table_of_sets->setValue((RawHashTable_Key) (entry_buffer->d_name), (RawHashTable_Value) body_set);
}
}
xfree(entry_buffer);
ink_closedir(dir);
return (new_table_of_sets);
}
// LOCKING: must be called with lock taken
HttpBodySet *
HttpBodyFactory::load_body_set_from_directory(char *set_name, char *tmpl_dir)
{
DIR *dir;
int status;
struct stat stat_buf;
char path[MAXPATHLEN + 1];
struct dirent *entry_buffer, *result;
////////////////////////////////////////////////
// ensure we can open tmpl_dir as a directory //
////////////////////////////////////////////////
Debug("body_factory", " load_body_set_from_directory(%s)", tmpl_dir);
dir = ink_opendir(tmpl_dir);
if (dir == NULL)
return (NULL);
/////////////////////////////////////////////
// ensure a .body_factory_info file exists //
/////////////////////////////////////////////
ink_snprintf(path, sizeof(path), "%s" DIR_SEP ".body_factory_info", tmpl_dir);
status = stat(path, &stat_buf);
if ((status < 0) || !S_ISREG(stat_buf.st_mode)) {
ink_closedir(dir);
return (NULL);
}
Debug("body_factory", " found '%s'", path);
/////////////////////////////////////////////////////////////////
// create body set, and loop over template files, loading them //
/////////////////////////////////////////////////////////////////
HttpBodySet *body_set = NEW(new HttpBodySet);
body_set->init(set_name, tmpl_dir);
Debug("body_factory", " body_set = 0x%0X (set_name '%s', lang '%s', charset '%s')",
body_set, body_set->set_name, body_set->content_language, body_set->content_charset);
entry_buffer = (struct dirent *)
xmalloc(sizeof(struct dirent) + MAXPATHLEN + 1);
while ((ink_readdir_r(dir, entry_buffer, &result) == 0) && (result != NULL)) {
HttpBodyTemplate *tmpl;
///////////////////////////////////////////////////////////////
// all template files have name of the form <type>#<subtype> //
///////////////////////////////////////////////////////////////
if (strchr(entry_buffer->d_name, '#') == NULL)
continue;
ink_snprintf(path, sizeof(path), "%s/%s", tmpl_dir, entry_buffer->d_name);
status = stat(path, &stat_buf);
if (status != 0)
continue; // can't stat
if (!S_ISREG(stat_buf.st_mode))
continue; // not a file
////////////////////////////////
// read in this template file //
////////////////////////////////
tmpl = NEW(new HttpBodyTemplate());
if (!tmpl->load_from_file(tmpl_dir, entry_buffer->d_name)) {
delete tmpl;
} else {
Debug("body_factory", " %s -> 0x%0X", entry_buffer->d_name, tmpl);
body_set->set_template_by_name(entry_buffer->d_name, tmpl);
}
}
xfree(entry_buffer);
ink_closedir(dir);
return (body_set);
}
////////////////////////////////////////////////////////////////////////
//
// class HttpBodySet
//
////////////////////////////////////////////////////////////////////////
HttpBodySet::HttpBodySet()
{
magic = HTTP_BODY_SET_MAGIC;
set_name = NULL;
content_language = NULL;
content_charset = NULL;
table_of_pages = NULL;
}
HttpBodySet::~HttpBodySet()
{
if (set_name)
xfree(set_name);
if (content_language)
xfree(content_language);
if (content_charset)
xfree(content_charset);
if (table_of_pages)
delete table_of_pages;
}
int
HttpBodySet::init(char *set, char *dir)
{
int fd, lineno, lines_added = 0;
char info_path[MAXPATHLEN];
char buffer[1024], name[1025], value[1024];
ink_snprintf(info_path, sizeof(info_path), "%s" DIR_SEP ".body_factory_info", dir);
fd = ink_open(info_path, O_RDONLY | _O_ATTRIB_NORMAL);
if (fd < 0)
return (-1);
this->set_name = xstrdup(set);
if (this->table_of_pages)
delete(this->table_of_pages);
this->table_of_pages = NEW(new RawHashTable(RawHashTable_KeyType_String));
lineno = 0;
while (1) {
char *name_s, *name_e, *value_s, *value_e, *hash;
++lineno;
int bytes = ink_file_fd_readline(fd, sizeof(buffer), buffer);
if (bytes <= 0)
break;
///////////////////////////////////////////////
// chop anything on and after first '#' sign //
///////////////////////////////////////////////
hash = index(buffer, '#');
if (hash)
*hash = '\0';
////////////////////////////////
// find start and end of name //
////////////////////////////////
name_s = buffer;
while (*name_s && ParseRules::is_wslfcr(*name_s))
++name_s;
name_e = name_s;
while (*name_e && ParseRules::is_http_field_name(*name_e))
++name_e;
if (name_s == name_e)
continue; // blank line
/////////////////////////////////
// find start and end of value //
/////////////////////////////////
value_s = name_e;
while (*value_s && (ParseRules::is_wslfcr(*value_s)))
++value_s;
if (*value_s != ':') {
Warning("ignoring invalid body factory info line #%d in %s", info_path);
continue;
}
++value_s; // skip the colon
while (*value_s && (ParseRules::is_wslfcr(*value_s)))
++value_s;
value_e = buffer + strlen(buffer) - 1;
while ((value_e > value_s) && ParseRules::is_wslfcr(*(value_e - 1)))
--value_e;
/////////////////////////////////
// insert line into hash table //
/////////////////////////////////
memcpy(name, name_s, name_e - name_s);
name[name_e - name_s] = '\0';
memcpy(value, value_s, value_e - value_s);
value[value_e - value_s] = '\0';
//////////////////////////////////////////////////
// so far, we only support 2 pieces of metadata //
//////////////////////////////////////////////////
if (strcasecmp(name, "Content-Language") == 0) {
if (this->content_language)
xfree(this->content_language);
this->content_language = xstrdup(value);
} else if (strcasecmp(name, "Content-Charset") == 0) {
if (this->content_charset)
xfree(this->content_charset);
this->content_charset = xstrdup(value);
}
}
////////////////////////////////////////////////////
// fill in default language & charset, if not set //
////////////////////////////////////////////////////
if (!this->content_language) {
if (strcmp(set, "default") == 0)
this->content_language = xstrdup("en");
else
this->content_language = xstrdup(set);
}
if (!this->content_charset)
this->content_charset = xstrdup("iso-8859-1");
ink_close(fd);
return (lines_added);
}
HttpBodyTemplate *
HttpBodySet::get_template_by_name(const char *name)
{
RawHashTable_Value v;
Debug("body_factory", " calling get_template_by_name(%s)", name);
if (table_of_pages == NULL)
return (NULL);
if (table_of_pages->getValue((RawHashTable_Key) name, &v)) {
HttpBodyTemplate *t = (HttpBodyTemplate *) v;
if ((t == NULL) || (!t->is_sane()))
return (NULL);
Debug("body_factory", " get_template_by_name(%s) -> (file %s, length %d)",
name, t->template_pathname, t->byte_count);
return (t);
}
Debug("body_factory", " get_template_by_name(%s) -> NULL", name);
return (NULL);
}
void
HttpBodySet::set_template_by_name(const char *name, HttpBodyTemplate * t)
{
table_of_pages->setValue((RawHashTable_Key) name, (RawHashTable_Value) t);
}
////////////////////////////////////////////////////////////////////////
//
// class HttpBodyTemplate
//
////////////////////////////////////////////////////////////////////////
HttpBodyTemplate::HttpBodyTemplate()
{
magic = HTTP_BODY_TEMPLATE_MAGIC;
byte_count = 0;
template_buffer = NULL;
template_pathname = NULL;
}
HttpBodyTemplate::~HttpBodyTemplate()
{
reset();
}
void
HttpBodyTemplate::reset()
{
if (template_buffer != NULL)
xfree(template_buffer);
template_buffer = NULL;
byte_count = 0;
if (template_pathname != NULL)
xfree(template_pathname);
}
int
HttpBodyTemplate::load_from_file(char *dir, char *file)
{
int fd, status;
ssize_t bytes_read;
struct stat stat_buf;
char path[MAXPATHLEN + 1];
char *new_template_buffer;
unsigned int new_byte_count;
////////////////////////////////////
// ensure this is actually a file //
////////////////////////////////////
ink_snprintf(path, sizeof(path), "%s/%s", dir, file);
// coverity[fs_check_call]
status = stat(path, &stat_buf);
if (status != 0)
return (0);
if (!S_ISREG(stat_buf.st_mode))
return (0);
///////////////////
// open the file //
///////////////////
// coverity[toctou]
fd = ink_open(path, O_RDONLY | _O_ATTRIB_NORMAL);
if (fd < 0)
return (0);
////////////////////////////////////////
// read in the template file contents //
////////////////////////////////////////
new_byte_count = stat_buf.st_size;
new_template_buffer = (char *) xmalloc(new_byte_count + 1);
bytes_read = ink_read(fd, new_template_buffer, new_byte_count);
new_template_buffer[new_byte_count] = '\0';
ink_close(fd);
///////////////////////////
// check for read errors //
///////////////////////////
if (bytes_read != (int) new_byte_count) {
Warning("reading template file '%s', got %d bytes instead of %d (%s)",
path, bytes_read, new_byte_count, (strerror(errno) ? strerror(errno) : "unknown error"));
xfree(new_template_buffer);
return (0);
}
Debug("body_factory", " read %d bytes from '%s'", new_byte_count, path);
/////////////////////////////////
// actually commit the changes //
/////////////////////////////////
reset();
template_buffer = new_template_buffer;
byte_count = new_byte_count;
size_t pathlen = strlen(path) + 1;
template_pathname = (char *) xmalloc(pathlen);
ink_strncpy(template_pathname, path, pathlen);
return (1);
}
char *
HttpBodyTemplate::build_instantiated_buffer(HttpTransact::State * context, int *buflen_return)
{
char *buffer = NULL;
#ifndef INK_NO_LOG
Debug("body_factory_instantiation", " before instantiation: [%s]", template_buffer);
LogAccessHttp la(context->state_machine);
int ret = Log::access(&la);
NOWARN_UNUSED(ret);
buffer = resolve_logfield_string((LogAccess *) & la, template_buffer);
*buflen_return = ((buffer == NULL) ? 0 : strlen(buffer));
Debug("body_factory_instantiation", " after instantiation: [%s]", buffer);
Debug("body_factory", " returning %d byte instantiated buffer", *buflen_return);
#endif
return (buffer);
}