httpd: add optional support for error pages (by Pierre Metras
No binary (build failed)
—
(log yok)
| @@ -170,6 +170,19 @@ config FEATURE_HTTPD_ENCODE_URL_STR | |
| For example, httpd -e "<Hello World>" 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 | |
| @@ -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); |
| } | |
| /* | |