/* $Id: htmlerror.c,v 1.7 2003-08-01 00:14:34 rjkaes Exp $
*
* This file contains source code for the handling and display of
* HTML error pages with variable substitution.
*
* Copyright (C) 2003 Steven Young
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include "tinyproxy.h"
#include "common.h"
#include "buffer.h"
#include "conns.h"
#include "heap.h"
#include "htmlerror.h"
#include "network.h"
#include "utils.h"
/*
* Add an error number -> filename mapping to the errorpages list.
*/
#define ERRORNUM_BUFSIZE 8 /* this is more than required */
#define ERRPAGES_BUCKETCOUNT 16
int
add_new_errorpage(char *filepath, unsigned int errornum) {
char errornbuf[ERRORNUM_BUFSIZE];
config.errorpages = hashmap_create(ERRPAGES_BUCKETCOUNT);
if (!config.errorpages)
return(-1);
snprintf(errornbuf, ERRORNUM_BUFSIZE, "%u", errornum);
if (hashmap_insert(config.errorpages, errornbuf,
filepath, strlen(filepath) + 1) < 0)
return(-1);
return(0);
}
/*
* Get the file appropriate for a given error.
*/
static char*
get_html_file(unsigned int errornum) {
hashmap_iter result_iter;
char errornbuf[ERRORNUM_BUFSIZE];
char *key;
static char *val;
assert(errornum >= 100 && errornum < 1000);
if (!config.errorpages) return(config.errorpage_undef);
snprintf(errornbuf, ERRORNUM_BUFSIZE, "%u", errornum);
result_iter = hashmap_find(config.errorpages, errornbuf);
if (hashmap_is_end(config.errorpages, result_iter))
return(config.errorpage_undef);
if (hashmap_return_entry(config.errorpages, result_iter,
&key, (void **)&val) < 0)
return(config.errorpage_undef);
return(val);
}
/*
* Look up the value for a variable.
*/
static char*
lookup_variable(struct conn_s *connptr, char *varname) {
hashmap_iter result_iter;
char *key;
static char *data;
result_iter = hashmap_find(connptr->error_variables, varname);
if (hashmap_is_end(connptr->error_variables, result_iter))
return(NULL);
if (hashmap_return_entry(connptr->error_variables, result_iter,
&key, (void **)&data) < 0)
return(NULL);
return(data);
}
#define HTML_BUFSIZE 4096
/*
* Send an already-opened file to the client with variable substitution.
*/
int
send_html_file(FILE *infile, struct conn_s *connptr) {
char inbuf[HTML_BUFSIZE], *varstart = NULL, *p;
char *varval;
int in_variable = 0, writeret;
while(fgets(inbuf, HTML_BUFSIZE, infile) != NULL) {
for (p = inbuf; *p; p++) {
switch(*p) {
case '}':
if(in_variable) {
*p = '\0';
if(!(varval = lookup_variable(connptr, varstart)))
varval = "(unknown)";
writeret = write_message(connptr->client_fd, "%s",
varval);
if(writeret) return(writeret);
in_variable = 0;
} else {
writeret = write_message(connptr->client_fd, "%c", *p);
if (writeret) return(writeret);
}
break;
case '{':
/* a {{ will print a single {. If we are NOT
* already in a { variable, then proceed with
* setup. If we ARE already in a { variable,
* this code will fallthrough to the code that
* just dumps a character to the client fd.
*/
if(!in_variable) {
varstart = p+1;
in_variable++;
} else
in_variable = 0;
default:
if(!in_variable) {
writeret = write_message(connptr->client_fd, "%c",
*p);
if(writeret) return(writeret);
}
}
}
in_variable = 0;
}
return(0);
}
int
send_http_headers(struct conn_s *connptr, int code, char *message) {
char *headers = \
"HTTP/1.0 %d %s\r\n" \
"Server: %s/%s\r\n" \
"Content-Type: text/html\r\n" \
"Connection: close\r\n" \
"\r\n";
return(write_message(connptr->client_fd, headers,
code, message,
PACKAGE, VERSION));
}
/*
* Display an error to the client.
*/
int
send_http_error_message(struct conn_s *connptr)
{
char *error_file;
FILE *infile;
int ret;
char *fallback_error = \
"%s" \
"%s %s
" \
"The page you requested was unavailable. The error code is listed " \
"below. In addition, the HTML file which has been configured as the " \
"page to be displayed when an error of this type was unavailable, " \
"with the error code %d (%s). Please contact your administrator." \
"%s" \
"" \
"\r\n";
send_http_headers(connptr, connptr->error_number, connptr->error_string);
error_file = get_html_file(connptr->error_number);
if(!(infile = fopen(error_file, "r")))
return(write_message(connptr->client_fd, fallback_error,
connptr->error_string,
PACKAGE, VERSION,
errno, strerror(errno),
connptr->error_string));
ret = send_html_file(infile, connptr);
fclose(infile);
return(ret);
}
/*
* Add a key -> value mapping for HTML file substitution.
*/
#define ERRVAR_BUCKETCOUNT 16
int
add_error_variable(struct conn_s *connptr, char *key, char *val)
{
if(!connptr->error_variables)
if (!(connptr->error_variables = hashmap_create(ERRVAR_BUCKETCOUNT)))
return(-1);
if (hashmap_insert(connptr->error_variables, key, val,
strlen(val) + 1) < 0)
return(-1);
return(0);
}
#define ADD_VAR_RET(x, y) if(y) { if(add_error_variable(connptr, x, y) == -1) return(-1); }
/*
* Set some standard variables used by all HTML pages
*/
int
add_standard_vars(struct conn_s *connptr) {
char timebuf[30];
time_t global_time = time(NULL);
strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT",
gmtime(&global_time));
ADD_VAR_RET("request", connptr->request_line);
ADD_VAR_RET("cause", connptr->error_string);
ADD_VAR_RET("clientip", connptr->client_ip_addr);
ADD_VAR_RET("clienthost", connptr->client_string_addr);
ADD_VAR_RET("version", VERSION);
ADD_VAR_RET("package", PACKAGE);
ADD_VAR_RET("date", timebuf);
return(0);
}
/*
* Add the error information to the conn structure.
*/
int
indicate_http_error(struct conn_s* connptr, int number, char *message, ...)
{
va_list ap;
char *key, *val;
va_start(ap, message);
while((key = va_arg(ap, char *))) {
val = va_arg(ap, char *);
if(add_error_variable(connptr, key, val) == -1) {
va_end(ap);
return(-1);
}
}
connptr->error_number = number;
connptr->error_string = safestrdup(message);
va_end(ap);
return(add_standard_vars(connptr));
}