From e58e8d944417f5255352c6594784017ff7c6fa27 Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Tue, 21 Aug 2007 10:26:55 +0000 Subject: httpd: add optional support for error pages (by Pierre Metras ) --- networking/Config.in | 13 +++ networking/httpd.c | 238 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 166 insertions(+), 85 deletions(-) diff --git a/networking/Config.in b/networking/Config.in index 3013be676..5275adc5f 100644 --- a/networking/Config.in +++ b/networking/Config.in @@ -170,6 +170,19 @@ config FEATURE_HTTPD_ENCODE_URL_STR For example, httpd -e "" as "<Hello World>". +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 diff --git a/networking/httpd.c b/networking/httpd.c index 070e2a915..7e60fc252 100644 --- a/networking/httpd.c +++ b/networking/httpd.c @@ -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); } /* -- cgit v1.2.3