httpd: add optional support for error pages

(by Pierre Metras <genepi@sympatico.ca>)
This commit is contained in:
Denis Vlasenko 2007-08-21 10:26:55 +00:00
parent 45946f8b51
commit e58e8d9444
2 changed files with 166 additions and 85 deletions

View File

@ -170,6 +170,19 @@ config FEATURE_HTTPD_ENCODE_URL_STR
For example, httpd -e "<Hello World>" as
"&#60Hello&#32World&#62".
config FEATURE_HTTPD_ERROR_PAGES
bool "Enable support for custom error pages"
default n
depends on HTTPD
help
This option allows you to define custom error pages in
the configuration file instead of the default HTTP status
error pages. For instance, if you add the line:
E404:/path/e404.html
in the config file, the server will respond the specified
'/path/e404.html' file instead of the terse '404 NOT FOUND'
message.
config IFCONFIG
bool "ifconfig"
default n

View File

@ -42,6 +42,7 @@
* A:10.0.0.0/255.255.255.128 # Allow any address that previous set
* A:127.0.0.1 # Allow local loopback connections
* D:* # Deny from other IP connections
* E404:/path/e404.html # /path/e404.html is the 404 (not found) error page
* /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin/
* /adm:admin:setup # Require user admin, pwd setup on urls starting with /adm/
* /adm:toor:PaSsWd # or user toor, pwd PaSsWd on urls starting with /adm/
@ -84,6 +85,10 @@
* subdir http request, any merge is discarded when the process exits. As a
* result, the subdir settings only have a lifetime of a single request.
*
* Custom error pages can contain an absolute path or be relative to
* 'home_httpd'. Error pages are to be static files (no CGI or script). Error
* page can only be defined in the root configuration file and are not taken
* into account in local (directories) config files.
*
* If -c is not set, an attempt will be made to open the default
* root configuration file. If -c is set and the file is not found, the
@ -131,6 +136,84 @@ typedef struct Htaccess_IP {
int allow_deny;
} Htaccess_IP;
enum {
HTTP_OK = 200,
HTTP_MOVED_TEMPORARILY = 302,
HTTP_BAD_REQUEST = 400, /* malformed syntax */
HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */
HTTP_NOT_FOUND = 404,
HTTP_FORBIDDEN = 403,
HTTP_REQUEST_TIMEOUT = 408,
HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */
HTTP_INTERNAL_SERVER_ERROR = 500,
HTTP_CONTINUE = 100,
#if 0 /* future use */
HTTP_SWITCHING_PROTOCOLS = 101,
HTTP_CREATED = 201,
HTTP_ACCEPTED = 202,
HTTP_NON_AUTHORITATIVE_INFO = 203,
HTTP_NO_CONTENT = 204,
HTTP_MULTIPLE_CHOICES = 300,
HTTP_MOVED_PERMANENTLY = 301,
HTTP_NOT_MODIFIED = 304,
HTTP_PAYMENT_REQUIRED = 402,
HTTP_BAD_GATEWAY = 502,
HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
HTTP_RESPONSE_SETSIZE = 0xffffffff
#endif
};
static const uint16_t http_response_type[] = {
HTTP_OK,
HTTP_MOVED_TEMPORARILY,
HTTP_REQUEST_TIMEOUT,
HTTP_NOT_IMPLEMENTED,
#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
HTTP_UNAUTHORIZED,
#endif
HTTP_NOT_FOUND,
HTTP_BAD_REQUEST,
HTTP_FORBIDDEN,
HTTP_INTERNAL_SERVER_ERROR,
#if 0 /* not implemented */
HTTP_CREATED,
HTTP_ACCEPTED,
HTTP_NO_CONTENT,
HTTP_MULTIPLE_CHOICES,
HTTP_MOVED_PERMANENTLY,
HTTP_NOT_MODIFIED,
HTTP_BAD_GATEWAY,
HTTP_SERVICE_UNAVAILABLE,
#endif
};
static const struct {
const char *name;
const char *info;
} http_response[ARRAY_SIZE(http_response_type)] = {
{ "OK", NULL },
{ "Found", "Directories must end with a slash" }, /* ?? */
{ "Request Timeout", "No request appeared within 60 seconds" },
{ "Not Implemented", "The requested method is not recognized" },
#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
{ "Unauthorized", "" },
#endif
{ "Not Found", "The requested URL was not found" },
{ "Bad Request", "Unsupported method" },
{ "Forbidden", "" },
{ "Internal Server Error", "Internal Server Error" },
#if 0 /* not implemented */
{ "Created" },
{ "Accepted" },
{ "No Content" },
{ "Multiple Choices" },
{ "Moved Permanently" },
{ "Not Modified" },
{ "Bad Gateway", "" },
{ "Service Unavailable", "" },
#endif
};
struct globals {
int verbose; /* must be int (used by getopt32) */
smallint flg_deny_all;
@ -167,6 +250,9 @@ struct globals {
#define hdr_buf bb_common_bufsiz1
char *hdr_ptr;
int hdr_cnt;
#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
const char *http_error_page[ARRAY_SIZE(http_response_type)];
#endif
};
#define G (*ptr_to_globals)
#define verbose (G.verbose )
@ -192,6 +278,7 @@ struct globals {
#define iobuf (G.iobuf )
#define hdr_ptr (G.hdr_ptr )
#define hdr_cnt (G.hdr_cnt )
#define http_error_page (G.http_error_page )
#define INIT_G() do { \
PTR_TO_GLOBALS = xzalloc(sizeof(G)); \
USE_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
@ -200,70 +287,13 @@ struct globals {
} while (0)
typedef enum {
HTTP_OK = 200,
HTTP_MOVED_TEMPORARILY = 302,
HTTP_BAD_REQUEST = 400, /* malformed syntax */
HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */
HTTP_NOT_FOUND = 404,
HTTP_FORBIDDEN = 403,
HTTP_REQUEST_TIMEOUT = 408,
HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */
HTTP_INTERNAL_SERVER_ERROR = 500,
#if 0 /* future use */
HTTP_CONTINUE = 100,
HTTP_SWITCHING_PROTOCOLS = 101,
HTTP_CREATED = 201,
HTTP_ACCEPTED = 202,
HTTP_NON_AUTHORITATIVE_INFO = 203,
HTTP_NO_CONTENT = 204,
HTTP_MULTIPLE_CHOICES = 300,
HTTP_MOVED_PERMANENTLY = 301,
HTTP_NOT_MODIFIED = 304,
HTTP_PAYMENT_REQUIRED = 402,
HTTP_BAD_GATEWAY = 502,
HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
HTTP_RESPONSE_SETSIZE = 0xffffffff
#endif
} HttpResponseNum;
typedef struct {
HttpResponseNum type;
const char *name;
const char *info;
} HttpEnumString;
static const HttpEnumString httpResponseNames[] = {
{ HTTP_OK, "OK", NULL },
{ HTTP_MOVED_TEMPORARILY, "Found", "Directories must end with a slash." },
{ HTTP_REQUEST_TIMEOUT, "Request Timeout",
"No request appeared within a reasonable time period." },
{ HTTP_NOT_IMPLEMENTED, "Not Implemented",
"The requested method is not recognized by this server." },
#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
{ HTTP_UNAUTHORIZED, "Unauthorized", "" },
#endif
{ HTTP_NOT_FOUND, "Not Found",
"The requested URL was not found on this server." },
{ HTTP_BAD_REQUEST, "Bad Request", "Unsupported method." },
{ HTTP_FORBIDDEN, "Forbidden", "" },
{ HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error",
"Internal Server Error" },
#if 0 /* not implemented */
{ HTTP_CREATED, "Created" },
{ HTTP_ACCEPTED, "Accepted" },
{ HTTP_NO_CONTENT, "No Content" },
{ HTTP_MULTIPLE_CHOICES, "Multiple Choices" },
{ HTTP_MOVED_PERMANENTLY, "Moved Permanently" },
{ HTTP_NOT_MODIFIED, "Not Modified" },
{ HTTP_BAD_GATEWAY, "Bad Gateway", "" },
{ HTTP_SERVICE_UNAVAILABLE, "Service Unavailable", "" },
#endif
};
#define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1)
/* Prototypes */
static void send_file_and_exit(const char *url, int headers) ATTRIBUTE_NORETURN;
static void free_llist(has_next_ptr **pptr)
{
has_next_ptr *cur = *pptr;
@ -382,11 +412,13 @@ static int scan_ip_mask(const char *str, unsigned *ipp, unsigned *maskp)
* .ext:mime/type # new mime type not compiled into httpd
* [adAD]:from # ip address allow/deny, * for wildcard
* /path:user:pass # username/password
* Ennn:error.html # error page for status nnn
*
* Any previous IP rules are discarded.
* If the flag argument is not SUBDIR_PARSE then all /path and mime rules
* are also discarded. That is, previous settings are retained if flag is
* SUBDIR_PARSE.
* Error pages are only parsed on the main config file.
*
* path Path where to look for httpd.conf (without filename).
* flag Type of the parse request.
@ -486,18 +518,6 @@ static void parse_conf(const char *path, int flag)
if (*p0 == 'a')
*p0 = 'A';
else if (*p0 != 'D' && *p0 != 'A'
#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
&& *p0 != '/'
#endif
#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
&& *p0 != '.'
#endif
#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
&& *p0 != '*'
#endif
)
continue;
if (*p0 == 'A' || *p0 == 'D') {
/* storing current config IP line */
pip = xzalloc(sizeof(Htaccess_IP));
@ -527,6 +547,30 @@ static void parse_conf(const char *path, int flag)
}
continue;
}
#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
if (flag == FIRST_PARSE && *p0 == 'E') {
int i;
/* error status code */
int status = atoi(++p0);
/* c already points at the character following ':' in parse loop */
// c = strchr(p0, ':'); c++;
if (status < HTTP_CONTINUE) {
bb_error_msg("config error '%s' in '%s'", buf, cf);
continue;
}
/* then error page; find matching status */
for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
if (http_response_type[i] == status) {
http_error_page[i] = concat_path_file((*c == '/') ? NULL : home_httpd, c);
break;
}
}
continue;
}
#endif
#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
if (*p0 == '/') {
/* make full path from httpd root / current_path / config_line_path */
@ -813,24 +857,29 @@ static void log_and_exit(void)
* IE will puke big-time if the headers are not sent in one packet and the
* second packet is delayed for any reason.
* responseNum - the result code to send.
* Return result of write().
*/
static void send_headers(HttpResponseNum responseNum)
static void send_headers(int responseNum)
{
static const char RFC1123FMT[] ALIGN1 = "%a, %d %b %Y %H:%M:%S GMT";
const char *responseString = "";
const char *infoString = NULL;
const char *mime_type;
#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
const char *error_page = 0;
#endif
unsigned i;
time_t timer = time(0);
char tmp_str[80];
int len;
for (i = 0; i < ARRAY_SIZE(httpResponseNames); i++) {
if (httpResponseNames[i].type == responseNum) {
responseString = httpResponseNames[i].name;
infoString = httpResponseNames[i].info;
for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
if (http_response_type[i] == responseNum) {
responseString = http_response[i].name;
infoString = http_response[i].info;
#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
error_page = http_error_page[i];
#endif
break;
}
}
@ -862,6 +911,20 @@ static void send_headers(HttpResponseNum responseNum)
(g_query ? g_query : ""));
}
#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
if (error_page && !access(error_page, R_OK)) {
strcat(iobuf, "\r\n");
len += 2;
if (DEBUG)
fprintf(stderr, "headers: '%s'\n", iobuf);
full_write(1, iobuf, len);
if (DEBUG)
fprintf(stderr, "writing error page: '%s'\n", error_page);
return send_file_and_exit(error_page, FALSE);
}
#endif
if (ContentLength != -1) { /* file */
strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&last_mod));
len += sprintf(iobuf + len, "Last-Modified: %s\r\n%s %"OFF_FMT"d\r\n",
@ -885,8 +948,8 @@ static void send_headers(HttpResponseNum responseNum)
}
}
static void send_headers_and_exit(HttpResponseNum responseNum) ATTRIBUTE_NORETURN;
static void send_headers_and_exit(HttpResponseNum responseNum)
static void send_headers_and_exit(int responseNum) ATTRIBUTE_NORETURN;
static void send_headers_and_exit(int responseNum)
{
send_headers(responseNum);
log_and_exit();
@ -1279,9 +1342,12 @@ static void send_cgi_and_exit(
/*
* Send a file response to a HTTP request, and exit
*
* Parameters:
* const char *url The requested URL (with leading /).
* headers Don't send headers before if FALSE.
*/
static void send_file_and_exit(const char *url) ATTRIBUTE_NORETURN;
static void send_file_and_exit(const char *url)
static void send_file_and_exit(const char *url, int headers)
{
static const char *const suffixTable[] = {
/* Warning: shorter equivalent suffix in one line must be first */
@ -1350,10 +1416,12 @@ static void send_file_and_exit(const char *url)
if (f < 0) {
if (DEBUG)
bb_perror_msg("cannot open '%s'", url);
send_headers_and_exit(HTTP_NOT_FOUND);
if (headers)
send_headers_and_exit(HTTP_NOT_FOUND);
}
send_headers(HTTP_OK);
if (headers)
send_headers(HTTP_OK);
/* If you want to know about EPIPE below
* (happens if you abort downloads from local httpd): */
@ -1789,7 +1857,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
* }
*/
send_file_and_exit(tptr);
send_file_and_exit(tptr, TRUE);
}
/*