]> granicus.if.org Git - libevent/commitdiff
Add callback support for error pages
authornntrab <kolmar@hotmail.co.uk>
Tue, 9 Feb 2016 18:01:00 +0000 (18:01 +0000)
committerAzat Khuzhin <azat@libevent.org>
Sun, 12 Jan 2020 21:50:14 +0000 (00:50 +0300)
The existing error pages are very basic and don't allow for
multi-lingual support or for conformity with other pages in a web site.
The aim of the callback functionality is to allow custom error pages to
be supported for calls to evhttp_send_error() by both calling
applications and Libevent itself.

A backward-incompatible change has been made to the title of error pages
sent by evhttp_send_error(). The original version of the function used
the reason argument as part of the title. That might have unforeseen
side-effects if it contains HTML tags. Therefore the title has been
changed to always use the standard status text.

An example of the error callback can be found in this
[version](https://github.com/libevent/libevent/files/123607/http-server.zip)
of the 'http-server' sample. It will output error pages with very bright
backgrounds, the error code using a very large font size and the reason.

Closes: #323 (cherr-picked from PR)
http-internal.h
http.c
include/event2/http.h
sample/http-server.c
test/regress_http.c

index 3bf16b5b7eda816701439d51a0c6ab7a898d5ac0..697494b5bbb8223ea58db0dafe82bd6aae97101a 100644 (file)
@@ -171,11 +171,15 @@ struct evhttp {
           don't match. */
        void (*gencb)(struct evhttp_request *req, void *);
        void *gencbarg;
+
        struct bufferevent* (*bevcb)(struct event_base *, void *);
        void *bevcbarg;
        int (*newreqcb)(struct evhttp_request *req, void *);
        void *newreqcbarg;
 
+       int (*errorcb)(struct evhttp_request *, struct evbuffer *, int, const char *, void *);
+       void *errorcbarg;
+
        struct event_base *base;
 
        evhttp_ext_method_cb ext_method_cmp;
diff --git a/http.c b/http.c
index d2ba8130dfd14dc01ec842957186d6a625e6e984..09feb1965ae50f2b6c1bb62d1d00abc3f24ca4bb 100644 (file)
--- a/http.c
+++ b/http.c
@@ -2984,36 +2984,72 @@ evhttp_send_done(struct evhttp_connection *evcon, void *arg)
 /*
  * Returns an error page.
  */
-
 void
 evhttp_send_error(struct evhttp_request *req, int error, const char *reason)
 {
-
-#define ERR_FORMAT "<HTML><HEAD>\n" \
-           "<TITLE>%d %s</TITLE>\n" \
-           "</HEAD><BODY>\n" \
-           "<H1>%s</H1>\n" \
-           "</BODY></HTML>\n"
+#define ERR_FORMAT "<html><head>" \
+       "<title>%d %s</title>" \
+       "</head><body>" \
+       "<h1>%d %s</h1>%s" \
+       "</body></html>"
 
        struct evbuffer *buf = evbuffer_new();
+       struct evhttp *http = req->evcon->http_server;
+
        if (buf == NULL) {
                /* if we cannot allocate memory; we just drop the connection */
                evhttp_connection_free(req->evcon);
                return;
        }
-       if (reason == NULL) {
-               reason = evhttp_response_phrase_internal(error);
-       }
 
        evhttp_response_code_(req, error, reason);
 
-       evbuffer_add_printf(buf, ERR_FORMAT, error, reason, reason);
+       /* Output error using callback for connection's evhttp, if available */
+       if ((http->errorcb == NULL) ||
+           ((*http->errorcb)(req, buf, error, reason, http->errorcbarg) < 0))
+       {
+               const char *heading = evhttp_response_phrase_internal(error);
+
+               evbuffer_drain(buf, evbuffer_get_length(buf));
+               evbuffer_add_printf(buf, ERR_FORMAT,
+                  error, heading, error, heading,
+                  (reason ? reason : ""));
+       }
 
        evhttp_send_page_(req, buf);
 
        evbuffer_free(buf);
 #undef ERR_FORMAT
 }
+static void
+evhttp_send_notfound(struct evhttp_request *req, const char *url)
+{
+#define REASON_FORMAT "<p>The requested URL %s was not found on this server.</p>"
+       char   *escaped_url = NULL;
+       char   *reason = NULL;
+       size_t reason_len;
+
+       url = (url != NULL ? url : req->uri);
+       if (url != NULL)
+               escaped_url = evhttp_htmlescape(url);
+
+       if (escaped_url != NULL) {
+               reason_len = strlen(REASON_FORMAT)+strlen(escaped_url)+1;
+               reason = mm_malloc(reason_len);
+       }
+
+       if ((escaped_url != NULL) && (reason != NULL))
+               evutil_snprintf(reason, reason_len, REASON_FORMAT, escaped_url);
+
+       evhttp_send_error(req, HTTP_NOTFOUND, reason);
+
+       if (reason != NULL)
+               mm_free(reason);
+       if (escaped_url != NULL)
+               mm_free(escaped_url);
+#undef REASON_FORMAT
+}
+
 
 /* Requires that headers and response code are already set up */
 
@@ -3698,40 +3734,8 @@ evhttp_handle_request(struct evhttp_request *req, void *arg)
        if (http->gencb) {
                (*http->gencb)(req, http->gencbarg);
                return;
-       } else {
-               /* We need to send a 404 here */
-#define ERR_FORMAT "<html><head>" \
-                   "<title>404 Not Found</title>" \
-                   "</head><body>" \
-                   "<h1>Not Found</h1>" \
-                   "<p>The requested URL %s was not found on this server.</p>"\
-                   "</body></html>\n"
-
-               char *escaped_html;
-               struct evbuffer *buf;
-
-               if ((escaped_html = evhttp_htmlescape(req->uri)) == NULL) {
-                       evhttp_connection_free(req->evcon);
-                       return;
-               }
-
-               if ((buf = evbuffer_new()) == NULL) {
-                       mm_free(escaped_html);
-                       evhttp_connection_free(req->evcon);
-                       return;
-               }
-
-               evhttp_response_code_(req, HTTP_NOTFOUND, "Not Found");
-
-               evbuffer_add_printf(buf, ERR_FORMAT, escaped_html);
-
-               mm_free(escaped_html);
-
-               evhttp_send_page_(req, buf);
-
-               evbuffer_free(buf);
-#undef ERR_FORMAT
-       }
+       } else
+               evhttp_send_notfound(req, NULL);
 }
 
 /* Listener callback when a connection arrives at a server. */
@@ -4188,6 +4192,14 @@ evhttp_set_newreqcb(struct evhttp *http,
        http->newreqcb = cb;
        http->newreqcbarg = cbarg;
 }
+void
+evhttp_set_errorcb(struct evhttp *http,
+    int (*cb)(struct evhttp_request *, struct evbuffer *, int, const char *, void *),
+    void *cbarg)
+{
+       http->errorcb = cb;
+       http->errorcbarg = cbarg;
+}
 
 /*
  * Request related functions
index 24e26ed169d6bc9c1077a842ce80bfe86648bdc2..7d4d28815b657f48bc711fa1f4ca027832c780f2 100644 (file)
@@ -337,6 +337,33 @@ EVENT2_EXPORT_SYMBOL
 void evhttp_set_newreqcb(struct evhttp *http,
     int (*cb)(struct evhttp_request*, void *), void *arg);
 
+/**
+   Set a callback to output for any error pages sent for requests of a given
+   evhttp object.
+
+   You can use this to override the default error pages sent, allowing such
+   things as multi-lingual support or customization to match other pages.
+
+   The callback should use the supplied buffer to output the text for an
+   error page. If the callback returns a negative value or doesn't output
+   anything to the buffer, the default error page will be sent instead. The
+   buffer will be automatically be sent when the callback returns, so the
+   callback shouldn't do so itself.
+
+   Microsoft Internet Explorer may display its own error pages if ones sent by
+   an HTTP server are smaller than certain sizes, depending on the status code.
+   To reliably suppress this feature an error page should be at least 512
+   bytes in size.
+
+   @param http the evhttp server object for which to set the callback
+   @param cb the callback to invoke to format error pages
+   @param arg an context argument for the callback
+ */
+EVENT2_EXPORT_SYMBOL
+void evhttp_set_errorcb(struct evhttp *http,
+    int (*cb)(struct evhttp_request *req, struct evbuffer *buffer, int error, const char *reason, void *cbarg),
+    void *cbarg);
+
 /**
    Adds a virtual host to the http server.
 
index 049aabc4418ea2c6e936c358311ff47ed9cf02a3..09d829c62b473bc6c944d0cd2fe93f94ba5e6dbd 100644 (file)
@@ -329,7 +329,7 @@ send_document_cb(struct evhttp_request *req, void *arg)
        evhttp_send_reply(req, 200, "OK", evb);
        goto done;
 err:
-       evhttp_send_error(req, 404, "Document was not found");
+       evhttp_send_error(req, HTTP_NOTFOUND, NULL);
        if (fd>=0)
                close(fd);
 done:
index 226a9b64bb181670958c5224d01068cbe47eb721..4e0c9a3920fee064e22cc4bb15b3d5ec8f05a693 100644 (file)
@@ -5088,6 +5088,188 @@ http_timeout_read_client_test(void *arg)
                evhttp_free(http);
 }
 
+/*
+ * Error callback tests
+ */
+
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
+
+#define ERRCBFLAG_GENCB  (0x01)
+#define ERRCBFLAG_ERRCB  (0x02)
+#define ERRCBFLAG_BOTHCB (ERRCBFLAG_GENCB | ERRCBFLAG_ERRCB)
+
+struct error_callback_test {
+       unsigned char flags;
+       enum evhttp_cmd_type type;
+       int response_code;
+       int length;
+} error_callback_tests[] = {
+       {0                , EVHTTP_REQ_GET  , HTTP_NOTFOUND       , 152} , /* 0 */
+       {0                , EVHTTP_REQ_POST , HTTP_NOTIMPLEMENTED , 101} , /* 1 */
+       {ERRCBFLAG_GENCB  , EVHTTP_REQ_GET  , HTTP_NOTFOUND       , 89}  , /* 2 */
+       {ERRCBFLAG_GENCB  , EVHTTP_REQ_POST , HTTP_NOTIMPLEMENTED , 101} , /* 3 */
+       {ERRCBFLAG_ERRCB  , EVHTTP_REQ_GET  , HTTP_NOTFOUND       , 3}   , /* 4 */
+       {ERRCBFLAG_ERRCB  , EVHTTP_REQ_POST , HTTP_NOTIMPLEMENTED , 101} , /* 5 */
+       {ERRCBFLAG_BOTHCB , EVHTTP_REQ_GET  , HTTP_NOTFOUND       , 3}   , /* 6 */
+       {ERRCBFLAG_BOTHCB , EVHTTP_REQ_POST , HTTP_NOTIMPLEMENTED , 3}   , /* 7 */
+       {ERRCBFLAG_ERRCB  , EVHTTP_REQ_GET  , HTTP_NOTFOUND       , 0}   , /* 8 */
+       {ERRCBFLAG_ERRCB  , EVHTTP_REQ_GET  , HTTP_NOTFOUND       , 152} , /* 9 */
+};
+
+struct error_callback_state
+{
+       struct basic_test_data *data;
+       unsigned test_index;
+       struct error_callback_test *test;
+       int test_failed;
+};
+
+static void
+http_error_callback_gencb(struct evhttp_request *req, void *arg)
+{
+       struct error_callback_state *state_info = arg;
+
+       switch (state_info->test_index) {
+       case 2:
+       case 6:
+               evhttp_send_error(req, HTTP_NOTFOUND, NULL);
+               break;
+       default:
+               evhttp_send_error(req, HTTP_INTERNAL, NULL);
+               break;
+       }
+}
+
+static int
+http_error_callback_errorcb(struct evhttp_request *req, struct evbuffer *buf,
+    int error, const char *reason, void *arg)
+{
+       struct error_callback_state *state_info = arg;
+       int return_code = -1;
+
+       switch (state_info->test_index) {
+       case 4:
+       case 6:
+       case 7:
+               evbuffer_add_printf(buf, "%d", error);
+               return_code = 0;
+               break;
+       case 8: /* Add nothing to buffer and then return 0 to trigger default */
+               return_code = 0;
+               break;
+       case 9: /* Add text to buffer but then return -1 to trigger default */
+               evbuffer_add_printf(buf, "%d", error);
+               break;
+       default:
+               /* Do nothing */
+               break;
+       }
+
+       return return_code;
+}
+
+static void
+http_error_callback_test_done(struct evhttp_request *req, void *arg)
+{
+       struct evkeyvalq *headers;
+       const char *header_text;
+       struct error_callback_state *state_info = arg;
+       struct error_callback_test *test_info;
+
+       test_info = state_info->test;
+
+       tt_assert(req);
+       tt_int_op(evhttp_request_get_command(req), ==,
+           test_info->type);
+       tt_int_op(evhttp_request_get_response_code(req), ==,
+           test_info->response_code);
+
+       headers = evhttp_request_get_input_headers(req);
+       tt_assert(headers);
+       header_text  = evhttp_find_header(headers, "Content-Type");
+       tt_assert_msg(header_text, "Missing Content-Type");
+       tt_str_op(header_text, ==, "text/html");
+       tt_int_op(evbuffer_get_length(evhttp_request_get_input_buffer(req)),
+           ==, test_info->length);
+
+       event_base_loopexit(state_info->data->base, NULL);
+       return;
+
+end:
+       state_info->test_failed = 1;
+       event_base_loopexit(state_info->data->base, NULL);
+}
+
+static void
+http_error_callback_test(void *arg)
+{
+       struct basic_test_data *data = arg;
+       struct evhttp *http = NULL;
+       ev_uint16_t port = 0;
+       struct evhttp_connection *evcon = NULL;
+       struct evhttp_request *req = NULL;
+       struct error_callback_state state_info;
+
+       test_ok = 0;
+
+       http = http_setup(&port, data->base, 0);
+       evhttp_set_allowed_methods(http,
+           EVHTTP_REQ_GET |
+           EVHTTP_REQ_HEAD |
+           EVHTTP_REQ_PUT |
+           EVHTTP_REQ_DELETE);
+
+       evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port);
+       tt_assert(evcon);
+
+       /* also bind to local host */
+       evhttp_connection_set_local_address(evcon, "127.0.0.1");
+
+       /* Initialise the state info */
+       state_info.data         = data;
+       state_info.test         = error_callback_tests;
+       state_info.test_failed  = 0;
+
+       /* Perform all the tests */
+       for (state_info.test_index = 0;
+            (state_info.test_index < ARRAY_SIZE(error_callback_tests)) &&
+            (!state_info.test_failed);
+            state_info.test_index++)
+       {
+               evhttp_set_gencb(http,
+                   ((state_info.test->flags & ERRCBFLAG_GENCB) != 0 ?
+                   http_error_callback_gencb : NULL),
+                   &state_info);
+               evhttp_set_errorcb(http,
+                   ((state_info.test->flags & ERRCBFLAG_ERRCB) != 0 ?
+                   http_error_callback_errorcb : NULL),
+                   &state_info);
+
+               req = evhttp_request_new(http_error_callback_test_done,
+                   &state_info);
+               tt_assert(req);
+               if (evhttp_make_request(evcon, req, state_info.test->type,
+                       "/missing") == -1) {
+                       tt_abort_msg("Couldn't make request");
+                       exit(1);
+               }
+               event_base_dispatch(data->base);
+
+               if (!state_info.test_failed)
+                       test_ok++;
+               else
+                       tt_abort_printf(("Sub-test %d failed",
+                           state_info.test_index));
+               state_info.test++;
+       }
+
+end:
+       if (evcon)
+               evhttp_connection_free(evcon);
+       if (http)
+               evhttp_free(http);
+}
+
 static void http_add_output_buffer(int fd, short events, void *arg)
 {
        evbuffer_add(arg, POST_DATA, strlen(POST_DATA));
@@ -5272,6 +5454,7 @@ struct testcase_t http_testcases[] = {
 
        HTTP(write_during_read),
        HTTP(request_own),
+       HTTP(error_callback),
 
        HTTP(request_extra_body),