aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenis Vlasenko <vda.linux@googlemail.com>2007-08-21 10:26:55 +0000
committerDenis Vlasenko <vda.linux@googlemail.com>2007-08-21 10:26:55 +0000
commite58e8d944417f5255352c6594784017ff7c6fa27 (patch)
treea348127b1da388cc6665b08a8586c1794b283601
parent45946f8b513d9c292613ac08c3ddf4a89b915752 (diff)
downloadbusybox-w32-e58e8d944417f5255352c6594784017ff7c6fa27.tar.gz
busybox-w32-e58e8d944417f5255352c6594784017ff7c6fa27.tar.bz2
busybox-w32-e58e8d944417f5255352c6594784017ff7c6fa27.zip
httpd: add optional support for error pages
(by Pierre Metras <genepi@sympatico.ca>)
-rw-r--r--networking/Config.in13
-rw-r--r--networking/httpd.c238
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
170 For example, httpd -e "<Hello World>" as 170 For example, httpd -e "<Hello World>" as
171 "&#60Hello&#32World&#62". 171 "&#60Hello&#32World&#62".
172 172
173config FEATURE_HTTPD_ERROR_PAGES
174 bool "Enable support for custom error pages"
175 default n
176 depends on HTTPD
177 help
178 This option allows you to define custom error pages in
179 the configuration file instead of the default HTTP status
180 error pages. For instance, if you add the line:
181 E404:/path/e404.html
182 in the config file, the server will respond the specified
183 '/path/e404.html' file instead of the terse '404 NOT FOUND'
184 message.
185
173config IFCONFIG 186config IFCONFIG
174 bool "ifconfig" 187 bool "ifconfig"
175 default n 188 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 @@
42 * A:10.0.0.0/255.255.255.128 # Allow any address that previous set 42 * A:10.0.0.0/255.255.255.128 # Allow any address that previous set
43 * A:127.0.0.1 # Allow local loopback connections 43 * A:127.0.0.1 # Allow local loopback connections
44 * D:* # Deny from other IP connections 44 * D:* # Deny from other IP connections
45 * E404:/path/e404.html # /path/e404.html is the 404 (not found) error page
45 * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin/ 46 * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin/
46 * /adm:admin:setup # Require user admin, pwd setup on urls starting with /adm/ 47 * /adm:admin:setup # Require user admin, pwd setup on urls starting with /adm/
47 * /adm:toor:PaSsWd # or user toor, pwd PaSsWd on urls starting with /adm/ 48 * /adm:toor:PaSsWd # or user toor, pwd PaSsWd on urls starting with /adm/
@@ -84,6 +85,10 @@
84 * subdir http request, any merge is discarded when the process exits. As a 85 * subdir http request, any merge is discarded when the process exits. As a
85 * result, the subdir settings only have a lifetime of a single request. 86 * result, the subdir settings only have a lifetime of a single request.
86 * 87 *
88 * Custom error pages can contain an absolute path or be relative to
89 * 'home_httpd'. Error pages are to be static files (no CGI or script). Error
90 * page can only be defined in the root configuration file and are not taken
91 * into account in local (directories) config files.
87 * 92 *
88 * If -c is not set, an attempt will be made to open the default 93 * If -c is not set, an attempt will be made to open the default
89 * root configuration file. If -c is set and the file is not found, the 94 * root configuration file. If -c is set and the file is not found, the
@@ -131,6 +136,84 @@ typedef struct Htaccess_IP {
131 int allow_deny; 136 int allow_deny;
132} Htaccess_IP; 137} Htaccess_IP;
133 138
139enum {
140 HTTP_OK = 200,
141 HTTP_MOVED_TEMPORARILY = 302,
142 HTTP_BAD_REQUEST = 400, /* malformed syntax */
143 HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */
144 HTTP_NOT_FOUND = 404,
145 HTTP_FORBIDDEN = 403,
146 HTTP_REQUEST_TIMEOUT = 408,
147 HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */
148 HTTP_INTERNAL_SERVER_ERROR = 500,
149 HTTP_CONTINUE = 100,
150#if 0 /* future use */
151 HTTP_SWITCHING_PROTOCOLS = 101,
152 HTTP_CREATED = 201,
153 HTTP_ACCEPTED = 202,
154 HTTP_NON_AUTHORITATIVE_INFO = 203,
155 HTTP_NO_CONTENT = 204,
156 HTTP_MULTIPLE_CHOICES = 300,
157 HTTP_MOVED_PERMANENTLY = 301,
158 HTTP_NOT_MODIFIED = 304,
159 HTTP_PAYMENT_REQUIRED = 402,
160 HTTP_BAD_GATEWAY = 502,
161 HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
162 HTTP_RESPONSE_SETSIZE = 0xffffffff
163#endif
164};
165
166static const uint16_t http_response_type[] = {
167 HTTP_OK,
168 HTTP_MOVED_TEMPORARILY,
169 HTTP_REQUEST_TIMEOUT,
170 HTTP_NOT_IMPLEMENTED,
171#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
172 HTTP_UNAUTHORIZED,
173#endif
174 HTTP_NOT_FOUND,
175 HTTP_BAD_REQUEST,
176 HTTP_FORBIDDEN,
177 HTTP_INTERNAL_SERVER_ERROR,
178#if 0 /* not implemented */
179 HTTP_CREATED,
180 HTTP_ACCEPTED,
181 HTTP_NO_CONTENT,
182 HTTP_MULTIPLE_CHOICES,
183 HTTP_MOVED_PERMANENTLY,
184 HTTP_NOT_MODIFIED,
185 HTTP_BAD_GATEWAY,
186 HTTP_SERVICE_UNAVAILABLE,
187#endif
188};
189
190static const struct {
191 const char *name;
192 const char *info;
193} http_response[ARRAY_SIZE(http_response_type)] = {
194 { "OK", NULL },
195 { "Found", "Directories must end with a slash" }, /* ?? */
196 { "Request Timeout", "No request appeared within 60 seconds" },
197 { "Not Implemented", "The requested method is not recognized" },
198#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
199 { "Unauthorized", "" },
200#endif
201 { "Not Found", "The requested URL was not found" },
202 { "Bad Request", "Unsupported method" },
203 { "Forbidden", "" },
204 { "Internal Server Error", "Internal Server Error" },
205#if 0 /* not implemented */
206 { "Created" },
207 { "Accepted" },
208 { "No Content" },
209 { "Multiple Choices" },
210 { "Moved Permanently" },
211 { "Not Modified" },
212 { "Bad Gateway", "" },
213 { "Service Unavailable", "" },
214#endif
215};
216
134struct globals { 217struct globals {
135 int verbose; /* must be int (used by getopt32) */ 218 int verbose; /* must be int (used by getopt32) */
136 smallint flg_deny_all; 219 smallint flg_deny_all;
@@ -167,6 +250,9 @@ struct globals {
167#define hdr_buf bb_common_bufsiz1 250#define hdr_buf bb_common_bufsiz1
168 char *hdr_ptr; 251 char *hdr_ptr;
169 int hdr_cnt; 252 int hdr_cnt;
253#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
254 const char *http_error_page[ARRAY_SIZE(http_response_type)];
255#endif
170}; 256};
171#define G (*ptr_to_globals) 257#define G (*ptr_to_globals)
172#define verbose (G.verbose ) 258#define verbose (G.verbose )
@@ -192,6 +278,7 @@ struct globals {
192#define iobuf (G.iobuf ) 278#define iobuf (G.iobuf )
193#define hdr_ptr (G.hdr_ptr ) 279#define hdr_ptr (G.hdr_ptr )
194#define hdr_cnt (G.hdr_cnt ) 280#define hdr_cnt (G.hdr_cnt )
281#define http_error_page (G.http_error_page )
195#define INIT_G() do { \ 282#define INIT_G() do { \
196 PTR_TO_GLOBALS = xzalloc(sizeof(G)); \ 283 PTR_TO_GLOBALS = xzalloc(sizeof(G)); \
197 USE_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \ 284 USE_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
@@ -200,70 +287,13 @@ struct globals {
200} while (0) 287} while (0)
201 288
202 289
203typedef enum {
204 HTTP_OK = 200,
205 HTTP_MOVED_TEMPORARILY = 302,
206 HTTP_BAD_REQUEST = 400, /* malformed syntax */
207 HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */
208 HTTP_NOT_FOUND = 404,
209 HTTP_FORBIDDEN = 403,
210 HTTP_REQUEST_TIMEOUT = 408,
211 HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */
212 HTTP_INTERNAL_SERVER_ERROR = 500,
213#if 0 /* future use */
214 HTTP_CONTINUE = 100,
215 HTTP_SWITCHING_PROTOCOLS = 101,
216 HTTP_CREATED = 201,
217 HTTP_ACCEPTED = 202,
218 HTTP_NON_AUTHORITATIVE_INFO = 203,
219 HTTP_NO_CONTENT = 204,
220 HTTP_MULTIPLE_CHOICES = 300,
221 HTTP_MOVED_PERMANENTLY = 301,
222 HTTP_NOT_MODIFIED = 304,
223 HTTP_PAYMENT_REQUIRED = 402,
224 HTTP_BAD_GATEWAY = 502,
225 HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
226 HTTP_RESPONSE_SETSIZE = 0xffffffff
227#endif
228} HttpResponseNum;
229
230typedef struct {
231 HttpResponseNum type;
232 const char *name;
233 const char *info;
234} HttpEnumString;
235
236static const HttpEnumString httpResponseNames[] = {
237 { HTTP_OK, "OK", NULL },
238 { HTTP_MOVED_TEMPORARILY, "Found", "Directories must end with a slash." },
239 { HTTP_REQUEST_TIMEOUT, "Request Timeout",
240 "No request appeared within a reasonable time period." },
241 { HTTP_NOT_IMPLEMENTED, "Not Implemented",
242 "The requested method is not recognized by this server." },
243#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
244 { HTTP_UNAUTHORIZED, "Unauthorized", "" },
245#endif
246 { HTTP_NOT_FOUND, "Not Found",
247 "The requested URL was not found on this server." },
248 { HTTP_BAD_REQUEST, "Bad Request", "Unsupported method." },
249 { HTTP_FORBIDDEN, "Forbidden", "" },
250 { HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error",
251 "Internal Server Error" },
252#if 0 /* not implemented */
253 { HTTP_CREATED, "Created" },
254 { HTTP_ACCEPTED, "Accepted" },
255 { HTTP_NO_CONTENT, "No Content" },
256 { HTTP_MULTIPLE_CHOICES, "Multiple Choices" },
257 { HTTP_MOVED_PERMANENTLY, "Moved Permanently" },
258 { HTTP_NOT_MODIFIED, "Not Modified" },
259 { HTTP_BAD_GATEWAY, "Bad Gateway", "" },
260 { HTTP_SERVICE_UNAVAILABLE, "Service Unavailable", "" },
261#endif
262};
263 290
264 291
265#define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1) 292#define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1)
266 293
294/* Prototypes */
295static void send_file_and_exit(const char *url, int headers) ATTRIBUTE_NORETURN;
296
267static void free_llist(has_next_ptr **pptr) 297static void free_llist(has_next_ptr **pptr)
268{ 298{
269 has_next_ptr *cur = *pptr; 299 has_next_ptr *cur = *pptr;
@@ -382,11 +412,13 @@ static int scan_ip_mask(const char *str, unsigned *ipp, unsigned *maskp)
382 * .ext:mime/type # new mime type not compiled into httpd 412 * .ext:mime/type # new mime type not compiled into httpd
383 * [adAD]:from # ip address allow/deny, * for wildcard 413 * [adAD]:from # ip address allow/deny, * for wildcard
384 * /path:user:pass # username/password 414 * /path:user:pass # username/password
415 * Ennn:error.html # error page for status nnn
385 * 416 *
386 * Any previous IP rules are discarded. 417 * Any previous IP rules are discarded.
387 * If the flag argument is not SUBDIR_PARSE then all /path and mime rules 418 * If the flag argument is not SUBDIR_PARSE then all /path and mime rules
388 * are also discarded. That is, previous settings are retained if flag is 419 * are also discarded. That is, previous settings are retained if flag is
389 * SUBDIR_PARSE. 420 * SUBDIR_PARSE.
421 * Error pages are only parsed on the main config file.
390 * 422 *
391 * path Path where to look for httpd.conf (without filename). 423 * path Path where to look for httpd.conf (without filename).
392 * flag Type of the parse request. 424 * flag Type of the parse request.
@@ -486,18 +518,6 @@ static void parse_conf(const char *path, int flag)
486 518
487 if (*p0 == 'a') 519 if (*p0 == 'a')
488 *p0 = 'A'; 520 *p0 = 'A';
489 else if (*p0 != 'D' && *p0 != 'A'
490#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
491 && *p0 != '/'
492#endif
493#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
494 && *p0 != '.'
495#endif
496#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
497 && *p0 != '*'
498#endif
499 )
500 continue;
501 if (*p0 == 'A' || *p0 == 'D') { 521 if (*p0 == 'A' || *p0 == 'D') {
502 /* storing current config IP line */ 522 /* storing current config IP line */
503 pip = xzalloc(sizeof(Htaccess_IP)); 523 pip = xzalloc(sizeof(Htaccess_IP));
@@ -527,6 +547,30 @@ static void parse_conf(const char *path, int flag)
527 } 547 }
528 continue; 548 continue;
529 } 549 }
550
551#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
552 if (flag == FIRST_PARSE && *p0 == 'E') {
553 int i;
554 /* error status code */
555 int status = atoi(++p0);
556 /* c already points at the character following ':' in parse loop */
557 // c = strchr(p0, ':'); c++;
558 if (status < HTTP_CONTINUE) {
559 bb_error_msg("config error '%s' in '%s'", buf, cf);
560 continue;
561 }
562
563 /* then error page; find matching status */
564 for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
565 if (http_response_type[i] == status) {
566 http_error_page[i] = concat_path_file((*c == '/') ? NULL : home_httpd, c);
567 break;
568 }
569 }
570 continue;
571 }
572#endif
573
530#if ENABLE_FEATURE_HTTPD_BASIC_AUTH 574#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
531 if (*p0 == '/') { 575 if (*p0 == '/') {
532 /* make full path from httpd root / current_path / config_line_path */ 576 /* make full path from httpd root / current_path / config_line_path */
@@ -813,24 +857,29 @@ static void log_and_exit(void)
813 * IE will puke big-time if the headers are not sent in one packet and the 857 * IE will puke big-time if the headers are not sent in one packet and the
814 * second packet is delayed for any reason. 858 * second packet is delayed for any reason.
815 * responseNum - the result code to send. 859 * responseNum - the result code to send.
816 * Return result of write().
817 */ 860 */
818static void send_headers(HttpResponseNum responseNum) 861static void send_headers(int responseNum)
819{ 862{
820 static const char RFC1123FMT[] ALIGN1 = "%a, %d %b %Y %H:%M:%S GMT"; 863 static const char RFC1123FMT[] ALIGN1 = "%a, %d %b %Y %H:%M:%S GMT";
821 864
822 const char *responseString = ""; 865 const char *responseString = "";
823 const char *infoString = NULL; 866 const char *infoString = NULL;
824 const char *mime_type; 867 const char *mime_type;
868#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
869 const char *error_page = 0;
870#endif
825 unsigned i; 871 unsigned i;
826 time_t timer = time(0); 872 time_t timer = time(0);
827 char tmp_str[80]; 873 char tmp_str[80];
828 int len; 874 int len;
829 875
830 for (i = 0; i < ARRAY_SIZE(httpResponseNames); i++) { 876 for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
831 if (httpResponseNames[i].type == responseNum) { 877 if (http_response_type[i] == responseNum) {
832 responseString = httpResponseNames[i].name; 878 responseString = http_response[i].name;
833 infoString = httpResponseNames[i].info; 879 infoString = http_response[i].info;
880#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
881 error_page = http_error_page[i];
882#endif
834 break; 883 break;
835 } 884 }
836 } 885 }
@@ -862,6 +911,20 @@ static void send_headers(HttpResponseNum responseNum)
862 (g_query ? g_query : "")); 911 (g_query ? g_query : ""));
863 } 912 }
864 913
914#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
915 if (error_page && !access(error_page, R_OK)) {
916 strcat(iobuf, "\r\n");
917 len += 2;
918
919 if (DEBUG)
920 fprintf(stderr, "headers: '%s'\n", iobuf);
921 full_write(1, iobuf, len);
922 if (DEBUG)
923 fprintf(stderr, "writing error page: '%s'\n", error_page);
924 return send_file_and_exit(error_page, FALSE);
925 }
926#endif
927
865 if (ContentLength != -1) { /* file */ 928 if (ContentLength != -1) { /* file */
866 strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&last_mod)); 929 strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&last_mod));
867 len += sprintf(iobuf + len, "Last-Modified: %s\r\n%s %"OFF_FMT"d\r\n", 930 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)
885 } 948 }
886} 949}
887 950
888static void send_headers_and_exit(HttpResponseNum responseNum) ATTRIBUTE_NORETURN; 951static void send_headers_and_exit(int responseNum) ATTRIBUTE_NORETURN;
889static void send_headers_and_exit(HttpResponseNum responseNum) 952static void send_headers_and_exit(int responseNum)
890{ 953{
891 send_headers(responseNum); 954 send_headers(responseNum);
892 log_and_exit(); 955 log_and_exit();
@@ -1279,9 +1342,12 @@ static void send_cgi_and_exit(
1279 1342
1280/* 1343/*
1281 * Send a file response to a HTTP request, and exit 1344 * Send a file response to a HTTP request, and exit
1345 *
1346 * Parameters:
1347 * const char *url The requested URL (with leading /).
1348 * headers Don't send headers before if FALSE.
1282 */ 1349 */
1283static void send_file_and_exit(const char *url) ATTRIBUTE_NORETURN; 1350static void send_file_and_exit(const char *url, int headers)
1284static void send_file_and_exit(const char *url)
1285{ 1351{
1286 static const char *const suffixTable[] = { 1352 static const char *const suffixTable[] = {
1287 /* Warning: shorter equivalent suffix in one line must be first */ 1353 /* Warning: shorter equivalent suffix in one line must be first */
@@ -1350,10 +1416,12 @@ static void send_file_and_exit(const char *url)
1350 if (f < 0) { 1416 if (f < 0) {
1351 if (DEBUG) 1417 if (DEBUG)
1352 bb_perror_msg("cannot open '%s'", url); 1418 bb_perror_msg("cannot open '%s'", url);
1353 send_headers_and_exit(HTTP_NOT_FOUND); 1419 if (headers)
1420 send_headers_and_exit(HTTP_NOT_FOUND);
1354 } 1421 }
1355 1422
1356 send_headers(HTTP_OK); 1423 if (headers)
1424 send_headers(HTTP_OK);
1357 1425
1358 /* If you want to know about EPIPE below 1426 /* If you want to know about EPIPE below
1359 * (happens if you abort downloads from local httpd): */ 1427 * (happens if you abort downloads from local httpd): */
@@ -1789,7 +1857,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
1789 * } 1857 * }
1790 */ 1858 */
1791 1859
1792 send_file_and_exit(tptr); 1860 send_file_and_exit(tptr, TRUE);
1793} 1861}
1794 1862
1795/* 1863/*