/* * Copyright (c) 2002-2007 Niels Provos <provos@citi.umich.edu> * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef EVENT__HAVE_STRSEP /* strsep replacement for platforms that lack it. Only works if
* del is one character long. */ staticchar *
strsep(char **s, constchar *del)
{ char *d, *tok;
EVUTIL_ASSERT(strlen(del) == 1); if (!s || !*s) return NULL;
tok = *s;
d = strstr(tok, del); if (d) {
*d = '\0';
*s = d + 1;
} else
*s = NULL; return tok;
} #endif
old_size = strlen(html); for (i = 0; i < old_size; ++i) { constchar *replaced = NULL; const size_t replace_size = html_replace(html[i], &replaced); if (replace_size > EV_SIZE_MAX - new_size) {
event_warn("%s: html_replace overflow", __func__); return (NULL);
}
new_size += replace_size;
}
if (new_size == EV_SIZE_MAX) return (NULL);
p = escaped_html = mm_malloc(new_size + 1); if (escaped_html == NULL) {
event_warn("%s: malloc(%lu)", __func__,
(unsignedlong)(new_size + 1)); return (NULL);
} for (i = 0; i < old_size; ++i) { constchar *replaced = &html[i]; const size_t len = html_replace(html[i], &replaced);
memcpy(p, replaced, len);
p += len;
}
*p = '\0';
return (escaped_html);
}
/** Given an evhttp_cmd_type, returns a constant string containing the * equivalent HTTP command, or NULL if the evhttp_command_type is
* unrecognized. */ staticconstchar *
evhttp_method(enum evhttp_cmd_type type)
{ constchar *method;
switch (type) { case EVHTTP_REQ_GET:
method = "GET"; break; case EVHTTP_REQ_POST:
method = "POST"; break; case EVHTTP_REQ_HEAD:
method = "HEAD"; break; case EVHTTP_REQ_PUT:
method = "PUT"; break; case EVHTTP_REQ_DELETE:
method = "DELETE"; break; case EVHTTP_REQ_OPTIONS:
method = "OPTIONS"; break; case EVHTTP_REQ_TRACE:
method = "TRACE"; break; case EVHTTP_REQ_CONNECT:
method = "CONNECT"; break; case EVHTTP_REQ_PATCH:
method = "PATCH"; break; default:
method = NULL; break;
}
return (method);
}
/** * Determines if a response should have a body. * Follows the rules in RFC 2616 section 4.3. * @return 1 if the response MUST have a body; 0 if the response MUST NOT have * a body.
*/ staticint
evhttp_response_needs_body(struct evhttp_request *req)
{ return (req->response_code != HTTP_NOCONTENT &&
req->response_code != HTTP_NOTMODIFIED &&
(req->response_code < 100 || req->response_code >= 200) &&
req->type != EVHTTP_REQ_CONNECT &&
req->type != EVHTTP_REQ_HEAD);
}
/** Helper: called after we've added some data to an evcon's bufferevent's * output buffer. Sets the evconn's writing-is-done callback, and puts * the bufferevent into writing mode.
*/ staticvoid
evhttp_write_buffer(struct evhttp_connection *evcon, void (*cb)(struct evhttp_connection *, void *), void *arg)
{
event_debug(("%s: preparing to write buffer\n", __func__));
/* Set call back */
evcon->cb = cb;
evcon->cb_arg = arg;
/* Disable the read callback: we don't actually care about data; * we only care about close detection. (We don't disable reading --
* EV_READ, since we *do* want to learn about any close events.) */
bufferevent_setcb(evcon->bufev,
NULL, /*read*/
evhttp_write_cb,
evhttp_error_cb,
evcon);
/** Helper: returns true iff evconn is in any connected state. */ staticint
evhttp_connected(struct evhttp_connection *evcon)
{ switch (evcon->state) { case EVCON_DISCONNECTED: case EVCON_CONNECTING: return (0); case EVCON_IDLE: case EVCON_READING_FIRSTLINE: case EVCON_READING_HEADERS: case EVCON_READING_BODY: case EVCON_READING_TRAILER: case EVCON_WRITING: default: return (1);
}
}
/* Create the headers needed for an outgoing HTTP request, adds them to * the request's header list, and writes the request line to the * connection's output buffer.
*/ staticvoid
evhttp_make_header_request(struct evhttp_connection *evcon, struct evhttp_request *req)
{ constchar *method;
/* Add the content length on a post or put request if missing */ if ((req->type == EVHTTP_REQ_POST || req->type == EVHTTP_REQ_PUT) &&
evhttp_find_header(req->output_headers, "Content-Length") == NULL){ char size[22];
evutil_snprintf(size, sizeof(size), EV_SIZE_FMT,
EV_SIZE_ARG(evbuffer_get_length(req->output_buffer)));
evhttp_add_header(req->output_headers, "Content-Length", size);
}
}
/** Return true if the list of headers in 'headers', intepreted with respect * to flags, means that we should send a "connection: close" when the request
* is done. */ staticint
evhttp_is_connection_close(int flags, struct evkeyvalq* headers)
{ if (flags & EVHTTP_PROXY_REQUEST) { /* proxy connection */ constchar *connection = evhttp_find_header(headers, "Proxy-Connection"); return (connection == NULL || evutil_ascii_strcasecmp(connection, "keep-alive") != 0);
} else { constchar *connection = evhttp_find_header(headers, "Connection"); return (connection != NULL && evutil_ascii_strcasecmp(connection, "close") == 0);
}
} staticint
evhttp_is_request_connection_close(struct evhttp_request *req)
{ if (req->type == EVHTTP_REQ_CONNECT) return 0;
/* Add a correct "Date" header to headers, unless it already has one. */ staticvoid
evhttp_maybe_add_date_header(struct evkeyvalq *headers)
{ if (evhttp_find_header(headers, "Date") == NULL) { char date[50]; if (sizeof(date) - evutil_date_rfc1123(date, sizeof(date), NULL) > 0) {
evhttp_add_header(headers, "Date", date);
}
}
}
/* Add a "Content-Length" header with value 'content_length' to headers,
* unless it already has a content-length or transfer-encoding header. */ staticvoid
evhttp_maybe_add_content_length_header(struct evkeyvalq *headers,
size_t content_length)
{ if (evhttp_find_header(headers, "Transfer-Encoding") == NULL &&
evhttp_find_header(headers, "Content-Length") == NULL) { char len[22];
evutil_snprintf(len, sizeof(len), EV_SIZE_FMT,
EV_SIZE_ARG(content_length));
evhttp_add_header(headers, "Content-Length", len);
}
}
/* * Create the headers needed for an HTTP reply in req->output_headers, * and write the first HTTP response for req line to evcon.
*/ staticvoid
evhttp_make_header_response(struct evhttp_connection *evcon, struct evhttp_request *req)
{ int is_keepalive = evhttp_is_connection_keepalive(req->input_headers);
evbuffer_add_printf(bufferevent_get_output(evcon->bufev), "HTTP/%d.%d %d %s\r\n",
req->major, req->minor, req->response_code,
req->response_code_line);
if (req->major == 1) { if (req->minor >= 1)
evhttp_maybe_add_date_header(req->output_headers);
/* * if the protocol is 1.0; and the connection was keep-alive * we need to add a keep-alive header, too.
*/ if (req->minor == 0 && is_keepalive)
evhttp_add_header(req->output_headers, "Connection", "keep-alive");
if ((req->minor >= 1 || is_keepalive) &&
evhttp_response_needs_body(req)) { /* * we need to add the content length if the * user did not give it, this is required for * persistent connections to work.
*/
evhttp_maybe_add_content_length_header(
req->output_headers,
evbuffer_get_length(req->output_buffer));
}
}
/* Potentially add headers for unidentified content. */ if (evhttp_response_needs_body(req)) { if (evhttp_find_header(req->output_headers, "Content-Type") == NULL
&& evcon->http_server->default_content_type) {
evhttp_add_header(req->output_headers, "Content-Type",
evcon->http_server->default_content_type);
}
}
/* if the request asked for a close, we send a close, too */ if (evhttp_is_connection_close(req->flags, req->input_headers)) {
evhttp_remove_header(req->output_headers, "Connection"); if (!(req->flags & EVHTTP_PROXY_REQUEST))
evhttp_add_header(req->output_headers, "Connection", "close");
evhttp_remove_header(req->output_headers, "Proxy-Connection");
}
}
enum expect { NO, CONTINUE, OTHER }; staticenum expect evhttp_have_expect(struct evhttp_request *req, int input)
{ constchar *expect; struct evkeyvalq *h = input ? req->input_headers : req->output_headers;
if (!(req->kind == EVHTTP_REQUEST) || !REQ_VERSION_ATLEAST(req, 1, 1)) return NO;
expect = evhttp_find_header(h, "Expect"); if (!expect) return NO;
/** Generate all headers appropriate for sending the http request in req (or * the response, if we're sending a response), and write them to evcon's
* bufferevent. Also writes all data from req->output_buffer */ staticvoid
evhttp_make_header(struct evhttp_connection *evcon, struct evhttp_request *req)
{ struct evkeyval *header; struct evbuffer *output = bufferevent_get_output(evcon->bufev);
/* * Depending if this is a HTTP request or response, we might need to * add some new headers or remove existing headers.
*/ if (req->kind == EVHTTP_REQUEST) {
evhttp_make_header_request(evcon, req);
} else {
evhttp_make_header_response(evcon, req);
}
if (evhttp_have_expect(req, 0) != CONTINUE &&
evbuffer_get_length(req->output_buffer)) { /* * For a request, we add the POST data, for a reply, this * is the regular data.
*/
evbuffer_add_buffer(output, req->output_buffer);
}
}
switch (error) { case EVREQ_HTTP_TIMEOUT: case EVREQ_HTTP_EOF: /* * these are cases in which we probably should just * close the connection and not send a reply. this * case may happen when a browser keeps a persistent * connection open and we timeout on the read. when * the request is still being used for sending, we * need to disassociated it from the connection here.
*/ if (!req->userdone) { /* remove it so that it will not be freed */
TAILQ_REMOVE(&req->evcon->requests, req, next); /* indicate that this request no longer has a * connection object
*/
req->evcon = NULL;
} return (-1); case EVREQ_HTTP_INVALID_HEADER: case EVREQ_HTTP_BUFFER_ERROR: case EVREQ_HTTP_REQUEST_CANCEL: case EVREQ_HTTP_DATA_TOO_LONG: default: /* xxx: probably should just error on default */ /* the callback looks at the uri to determine errors */ if (req->uri) {
mm_free(req->uri);
req->uri = NULL;
} if (req->uri_elems) {
evhttp_uri_free(req->uri_elems);
req->uri_elems = NULL;
}
/* * the callback needs to send a reply, once the reply has * been send, the connection should get freed.
*/
(*req->cb)(req, req->cb_arg);
}
return (0);
}
/* Free connection ownership of which can be acquired by user using
* evhttp_request_own(). */ staticinlinevoid
evhttp_request_free_auto(struct evhttp_request *req)
{ if (!(req->flags & EVHTTP_USER_OWNED))
evhttp_request_free(req);
}
/* Called when evcon has experienced a (non-recoverable? -NM) error, as * given in error. If it's an outgoing connection, reset the connection, * retry any pending requests, and inform the user. If it's incoming,
* delegates to evhttp_connection_incoming_fail(). */ void
evhttp_connection_fail_(struct evhttp_connection *evcon, enum evhttp_request_error error)
{ constint errsave = EVUTIL_SOCKET_ERROR(); struct evhttp_request* req = TAILQ_FIRST(&evcon->requests); void (*cb)(struct evhttp_request *, void *); void *cb_arg; void (*error_cb)(enum evhttp_request_error, void *); void *error_cb_arg;
EVUTIL_ASSERT(req != NULL);
if (evcon->flags & EVHTTP_CON_INCOMING) { /* * for incoming requests, there are two different * failure cases. it's either a network level error * or an http layer error. for problems on the network * layer like timeouts we just drop the connections. * For HTTP problems, we might have to send back a * reply before the connection can be freed.
*/ if (evhttp_connection_incoming_fail(req, error) == -1)
evhttp_connection_free(evcon); return;
}
error_cb = req->error_cb;
error_cb_arg = req->cb_arg; /* when the request was canceled, the callback is not executed */ if (error != EVREQ_HTTP_REQUEST_CANCEL) { /* save the callback for later; the cb might free our object */
cb = req->cb;
cb_arg = req->cb_arg;
} else {
cb = NULL;
cb_arg = NULL;
}
/* do not fail all requests; the next request is going to get * send over a new connection. when a user cancels a request, * all other pending requests should be processed as normal
*/
evhttp_request_free_(evcon, req);
/* reset the connection */
evhttp_connection_reset_(evcon);
/* We are trying the next request that was queued on us */ if (TAILQ_FIRST(&evcon->requests) != NULL)
evhttp_connection_connect_(evcon); else if ((evcon->flags & EVHTTP_CON_OUTGOING) &&
(evcon->flags & EVHTTP_CON_AUTOFREE)) {
evhttp_connection_free(evcon);
}
/* The call to evhttp_connection_reset_ overwrote errno. * Let's restore the original errno, so that the user's * callback can have a better idea of what the error was.
*/
EVUTIL_SET_SOCKET_ERROR(errsave);
/* inform the user */ if (error_cb != NULL)
error_cb(error, error_cb_arg); if (cb != NULL)
(*cb)(NULL, cb_arg);
}
/* Bufferevent callback: invoked when any data has been written from an
* http connection's bufferevent */ staticvoid
evhttp_write_cb(struct bufferevent *bufev, void *arg)
{ struct evhttp_connection *evcon = arg;
/* Activate our call back */ if (evcon->cb != NULL)
(*evcon->cb)(evcon, evcon->cb_arg);
}
/** * Advance the connection state. * - If this is an outgoing connection, we've just processed the response; * idle or close the connection. * - If this is an incoming connection, we've just processed the request; * respond.
*/ staticvoid
evhttp_connection_done(struct evhttp_connection *evcon)
{ struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); int con_outgoing = evcon->flags & EVHTTP_CON_OUTGOING; int free_evcon = 0;
if (con_outgoing) { /* idle or close the connection */ int need_close = evhttp_is_request_connection_close(req);
TAILQ_REMOVE(&evcon->requests, req, next);
req->evcon = NULL;
evcon->state = EVCON_IDLE;
/* check if we got asked to close the connection */ if (need_close)
evhttp_connection_reset_(evcon);
if (TAILQ_FIRST(&evcon->requests) != NULL) { /* * We have more requests; reset the connection * and deal with the next request.
*/ if (!evhttp_connected(evcon))
evhttp_connection_connect_(evcon); else
evhttp_request_dispatch(evcon);
} elseif (!need_close) { /* * The connection is going to be persistent, but we * need to detect if the other side closes it.
*/
evhttp_connection_start_detectclose(evcon);
} elseif ((evcon->flags & EVHTTP_CON_AUTOFREE)) { /* * If we have no more requests that need completion * and we're not waiting for the connection to close
*/
free_evcon = 1;
}
} else { /* * incoming connection - we need to leave the request on the * connection so that we can reply to it.
*/
evcon->state = EVCON_WRITING;
}
/* notify the user of the request */
(*req->cb)(req, req->cb_arg);
/* if this was an outgoing request, we own and it's done. so free it. */ if (con_outgoing) {
evhttp_request_free_auto(req);
}
/* If this was the last request of an outgoing connection and we're * not waiting to receive a connection close event and we want to * automatically free the connection. We check to ensure our request * list is empty one last time just in case our callback added a * new request.
*/ if (free_evcon && TAILQ_FIRST(&evcon->requests) == NULL) {
evhttp_connection_free(evcon);
}
}
/* * Handles reading from a chunked request. * return ALL_DATA_READ: * all data has been read * return MORE_DATA_EXPECTED: * more data is expected * return DATA_CORRUPTED: * data is corrupted * return REQUEST_CANCELED: * request was canceled by the user calling evhttp_cancel_request * return DATA_TOO_LONG: * ran over the maximum limit
*/
if ((buflen = evbuffer_get_length(buf)) == 0) { break;
}
/* evbuffer_get_length returns size_t, but len variable is ssize_t,
* check for overflow conditions */ if (buflen > EV_SSIZE_MAX) { return DATA_CORRUPTED;
}
if (req->ntoread < 0) { /* Read chunk size */
ev_int64_t ntoread; char *p = evbuffer_readln(buf, NULL, EVBUFFER_EOL_CRLF); char *endp; int error; if (p == NULL) break; /* the last chunk is on a new line? */ if (strlen(p) == 0) {
mm_free(p); continue;
}
ntoread = evutil_strtoll(p, &endp, 16);
error = (*p == '\0' ||
(*endp != '\0' && *endp != ' ') ||
ntoread < 0);
mm_free(p); if (error) { /* could not get chunk size */ return (DATA_CORRUPTED);
}
/* ntoread is signed int64, body_size is unsigned size_t, check for under/overflow conditions */ if ((ev_uint64_t)ntoread > EV_SIZE_MAX - req->body_size) { return DATA_CORRUPTED;
}
if (req->body_size + (size_t)ntoread > req->evcon->max_body_size) { /* failed body length test */
event_debug(("Request body is too long")); return (DATA_TOO_LONG);
}
req->body_size += (size_t)ntoread;
req->ntoread = ntoread; if (req->ntoread == 0) { /* Last chunk */ return (ALL_DATA_READ);
} continue;
}
/* req->ntoread is signed int64, len is ssize_t, based on arch,
* ssize_t could only be 32b, check for these conditions */ if (req->ntoread > EV_SSIZE_MAX) { return DATA_CORRUPTED;
}
/* don't have enough to complete a chunk; wait for more */ if (req->ntoread > 0 && buflen < (ev_uint64_t)req->ntoread) return (MORE_DATA_EXPECTED);
if (req->chunked) { switch (evhttp_handle_chunked_read(req, buf)) { case ALL_DATA_READ: /* finished last chunk */
evcon->state = EVCON_READING_TRAILER;
evhttp_read_trailer(evcon, req); return; case DATA_CORRUPTED: case DATA_TOO_LONG: /* corrupted data */
evhttp_connection_fail_(evcon,
EVREQ_HTTP_DATA_TOO_LONG); return; case REQUEST_CANCELED: /* request canceled */
evhttp_request_free_auto(req); return; case MORE_DATA_EXPECTED: default: break;
}
} elseif (req->ntoread < 0) { /* Read until connection close. */ if ((size_t)(req->body_size + evbuffer_get_length(buf)) < req->body_size) {
evhttp_connection_fail_(evcon, EVREQ_HTTP_INVALID_HEADER); return;
}
req->body_size += evbuffer_get_length(buf);
evbuffer_add_buffer(req->input_buffer, buf);
} elseif (req->chunk_cb != NULL || evbuffer_get_length(buf) >= (size_t)req->ntoread) { /* XXX: the above get_length comparison has to be fixed for overflow conditions! */ /* We've postponed moving the data until now, but we're
* about to use it. */
size_t n = evbuffer_get_length(buf);
if (n > (size_t) req->ntoread)
n = (size_t) req->ntoread;
req->ntoread -= n;
req->body_size += n;
evbuffer_remove_buffer(buf, req->input_buffer, n);
}
if (req->body_size > req->evcon->max_body_size ||
(!req->chunked && req->ntoread >= 0 &&
(size_t)req->ntoread > req->evcon->max_body_size)) { /* XXX: The above casted comparison must checked for overflow */ /* failed body length test */
/* Cancel if it's pending. */
event_deferred_cb_cancel_(get_deferred_queue(evcon),
&evcon->read_more_deferred_cb);
switch (evcon->state) { case EVCON_READING_FIRSTLINE:
evhttp_read_firstline(evcon, req); /* note the request may have been freed in
* evhttp_read_body */ break; case EVCON_READING_HEADERS:
evhttp_read_header(evcon, req); /* note the request may have been freed in
* evhttp_read_body */ break; case EVCON_READING_BODY:
evhttp_read_body(evcon, req); /* note the request may have been freed in
* evhttp_read_body */ break; case EVCON_READING_TRAILER:
evhttp_read_trailer(evcon, req); break; case EVCON_IDLE:
{ #ifdef USE_DEBUG struct evbuffer *input;
size_t total_len;
evhttp_connection_reset_(evcon);
} break; case EVCON_DISCONNECTED: case EVCON_CONNECTING: case EVCON_WRITING: default:
event_errx(1, "%s: illegal connection state %d",
__func__, evcon->state);
}
}
staticvoid
evhttp_write_connectioncb(struct evhttp_connection *evcon, void *arg)
{ /* This is after writing the request to the server */ struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); struct evbuffer *output = bufferevent_get_output(evcon->bufev);
EVUTIL_ASSERT(req != NULL);
EVUTIL_ASSERT(evcon->state == EVCON_WRITING);
/* We need to wait until we've written all of our output data before we can
* continue */ if (evbuffer_get_length(output) > 0) return;
/* We are done writing our header and are now expecting the response */
req->kind = EVHTTP_RESPONSE;
/* notify interested parties that this connection is going down */ if (evcon->fd != -1) { if (evhttp_connected(evcon) && evcon->closecb != NULL)
(*evcon->closecb)(evcon, evcon->closecb_arg);
}
/* remove all requests that might be queued on this * connection. for server connections, this should be empty. * because it gets dequeued either in evhttp_connection_done or * evhttp_connection_fail_.
*/ while ((req = TAILQ_FIRST(&evcon->requests)) != NULL) {
evhttp_request_free_(evcon, req);
}
/* XXXX This is not actually an optimal fix. Instead we ought to have an API for "stop connecting", or use bufferevent_setfd to turn off connecting. But for Libevent 2.0, this seems like a minimal change least likely to disrupt the rest of the bufferevent and http code.
Why is this here? If the fd is set in the bufferevent, and the bufferevent is connecting, then you can't actually stop the bufferevent from trying to connect with bufferevent_disable(). The connect will never trigger, since we close the fd, but the timeout might. That caused an assertion failure in evhttp_connection_fail_.
*/
bufferevent_disable_hard_(evcon->bufev, EV_READ|EV_WRITE);
if (evcon->fd == -1)
evcon->fd = bufferevent_getfd(evcon->bufev);
if (evcon->fd != -1) { /* inform interested parties about connection close */ if (evhttp_connected(evcon) && evcon->closecb != NULL)
(*evcon->closecb)(evcon, evcon->closecb_arg);
/* * User callback can do evhttp_make_request() on the same * evcon so new request will be added to evcon->requests. To * avoid freeing it prematurely we iterate over the copy of * the queue.
*/
TAILQ_INIT(&requests); while (TAILQ_FIRST(&evcon->requests) != NULL) { struct evhttp_request *request = TAILQ_FIRST(&evcon->requests);
TAILQ_REMOVE(&evcon->requests, request, next);
TAILQ_INSERT_TAIL(&requests, request, next);
}
/* for now, we just signal all requests by executing their callbacks */ while (TAILQ_FIRST(&requests) != NULL) { struct evhttp_request *request = TAILQ_FIRST(&requests);
TAILQ_REMOVE(&requests, request, next);
request->evcon = NULL;
/* we might want to set an error here */
request->cb(request, request->cb_arg);
evhttp_request_free_auto(request);
}
}
/** Second time, we can't read anything */ if (evcon->flags & EVHTTP_CON_READING_ERROR) {
evcon->flags &= ~EVHTTP_CON_READING_ERROR;
evhttp_connection_fail_(evcon, EVREQ_HTTP_EOF); return;
}
if (evcon->fd == -1)
evcon->fd = bufferevent_getfd(bufev);
switch (evcon->state) { case EVCON_CONNECTING: if (what & BEV_EVENT_TIMEOUT) {
event_debug(("%s: connection timeout for \"%s:%d\" on "
EV_SOCK_FMT,
__func__, evcon->address, evcon->port,
EV_SOCK_ARG(evcon->fd)));
evhttp_connection_cb_cleanup(evcon); return;
} break;
case EVCON_READING_BODY: if (!req->chunked && req->ntoread < 0
&& what == (BEV_EVENT_READING|BEV_EVENT_EOF)) { /* EOF on read can be benign */
evhttp_connection_done(evcon); return;
} break;
case EVCON_DISCONNECTED: case EVCON_IDLE: case EVCON_READING_FIRSTLINE: case EVCON_READING_HEADERS: case EVCON_READING_TRAILER: case EVCON_WRITING: default: break;
}
/* when we are in close detect mode, a read error means that * the other side closed their connection.
*/ if (evcon->flags & EVHTTP_CON_CLOSEDETECT) {
evcon->flags &= ~EVHTTP_CON_CLOSEDETECT;
EVUTIL_ASSERT(evcon->http_server == NULL); /* For connections from the client, we just * reset the connection so that it becomes * disconnected.
*/
EVUTIL_ASSERT(evcon->state == EVCON_IDLE);
evhttp_connection_reset_(evcon);
/* * If we have no more requests that need completion * and we want to auto-free the connection when all * requests have been completed.
*/ if (TAILQ_FIRST(&evcon->requests) == NULL
&& (evcon->flags & EVHTTP_CON_OUTGOING)
&& (evcon->flags & EVHTTP_CON_AUTOFREE)) {
evhttp_connection_free(evcon);
} return;
}
/* * Event callback for asynchronous connection attempt.
*/ staticvoid
evhttp_connection_cb(struct bufferevent *bufev, short what, void *arg)
{ struct evhttp_connection *evcon = arg; int error;
ev_socklen_t errsz = sizeof(error);
if (evcon->fd == -1)
evcon->fd = bufferevent_getfd(bufev);
if (!(what & BEV_EVENT_CONNECTED)) { /* some operating systems return ECONNREFUSED immediately * when connecting to a local address. the cleanup is going * to reschedule this function call.
*/ #ifndef _WIN32 if (errno == ECONNREFUSED) goto cleanup; #endif
evhttp_error_cb(bufev, what, arg); return;
}
if (evcon->fd == -1) {
event_debug(("%s: bufferevent_getfd returned -1",
__func__)); goto cleanup;
}
/* Check if the connection completed */ if (getsockopt(evcon->fd, SOL_SOCKET, SO_ERROR, (void*)&error,
&errsz) == -1) {
event_debug(("%s: getsockopt for \"%s:%d\" on "EV_SOCK_FMT,
__func__, evcon->address, evcon->port,
EV_SOCK_ARG(evcon->fd))); goto cleanup;
}
if (error) {
event_debug(("%s: connect failed for \"%s:%d\" on "
EV_SOCK_FMT": %s",
__func__, evcon->address, evcon->port,
EV_SOCK_ARG(evcon->fd),
evutil_socket_error_to_string(error))); goto cleanup;
}
/* We are connected to the server now */
event_debug(("%s: connected to \"%s:%d\" on "EV_SOCK_FMT"\n",
__func__, evcon->address, evcon->port,
EV_SOCK_ARG(evcon->fd)));
/* Reset the retry count as we were successful in connecting */
evcon->retry_cnt = 0;
evcon->state = EVCON_IDLE;
/* reset the bufferevent cbs */
bufferevent_setcb(evcon->bufev,
evhttp_read_cb,
evhttp_write_cb,
evhttp_error_cb,
evcon);
while (eos > line && *(eos-1) == ' ') {
*(eos-1) = '\0';
--eos;
--len;
} if (len < strlen("GET / HTTP/1.0")) return -1;
/* Parse the request line */
method = strsep(&line, " "); if (!line) return -1;
uri = line;
version = strrchr(uri, ' '); if (!version || uri == version) return -1;
*version = '\0';
version++;
method_len = (uri - method) - 1;
type = EVHTTP_REQ_UNKNOWN_;
/* First line */ switch (method_len) { case 3: /* The length of the method string is 3, meaning it can only be one of two methods: GET or PUT */
/* Since both GET and PUT share the same character 'T' at the end, * if the string doesn't have 'T', we can immediately determine this
* is an invalid HTTP method */
if (method[2] != 'T') { break;
}
switch (*method) { case'G': /* This first byte is 'G', so make sure the next byte is
* 'E', if it isn't then this isn't a valid method */
if (method[1] == 'E') {
type = EVHTTP_REQ_GET;
}
break; case'P': /* First byte is P, check second byte for 'U', if not,
* we know it's an invalid method */ if (method[1] == 'U') {
type = EVHTTP_REQ_PUT;
} break; default: break;
} break; case 4: /* The method length is 4 bytes, leaving only the methods "POST" and "HEAD" */ switch (*method) { case'P': if (method[3] == 'T' && method[2] == 'S' && method[1] == 'O') {
type = EVHTTP_REQ_POST;
} break; case'H': if (method[3] == 'D' && method[2] == 'A' && method[1] == 'E') {
type = EVHTTP_REQ_HEAD;
} break; default: break;
} break; case 5: /* Method length is 5 bytes, which can only encompass PATCH and TRACE */ switch (*method) { case'P': if (method[4] == 'H' && method[3] == 'C' && method[2] == 'T' && method[1] == 'A') {
type = EVHTTP_REQ_PATCH;
} break; case'T': if (method[4] == 'E' && method[3] == 'C' && method[2] == 'A' && method[1] == 'R') {
type = EVHTTP_REQ_TRACE;
}
break; default: break;
} break; case 6: /* Method length is 6, only valid method 6 bytes in length is DELEte */
/* If the first byte isn't 'D' then it's invalid */ if (*method != 'D') { break;
}
if ((int)type == EVHTTP_REQ_UNKNOWN_) {
event_debug(("%s: bad method %s on request %p from %s",
__func__, method, req, req->remote_host)); /* No error yet; we'll give a better error later when
* we see that req->type is unsupported. */
}
req->type = type;
if (evhttp_parse_http_version(version, req) < 0) return -1;
if (type == EVHTTP_REQ_CONNECT) { if ((req->uri_elems = evhttp_uri_parse_authority(req->uri)) == NULL) { return -1;
}
} else { if ((req->uri_elems = evhttp_uri_parse_with_flags(req->uri,
EVHTTP_URI_NONCONFORMANT)) == NULL) { return -1;
}
}
/* If we have an absolute-URI, check to see if it is an http request for a known vhost or server alias. If we don't know about this
host, we consider it a proxy request. */
scheme = evhttp_uri_get_scheme(req->uri_elems);
hostname = evhttp_uri_get_host(req->uri_elems); if (scheme && (!evutil_ascii_strcasecmp(scheme, "http") ||
!evutil_ascii_strcasecmp(scheme, "https")) &&
hostname &&
!evhttp_find_vhost(req->evcon->http_server, NULL, hostname))
req->flags |= EVHTTP_PROXY_REQUEST;
while ((p = strpbrk(p, "\r\n")) != NULL) { /* we really expect only one new line */
p += strspn(p, "\r\n"); /* we expect a space or tab for continuation */ if (*p != ' ' && *p != '\t') return (0);
} return (1);
}
/* * Parses header lines from a request or a response into the specified * request object given an event buffer. * * Returns * DATA_CORRUPTED on error * MORE_DATA_EXPECTED when we need to read more headers * ALL_DATA_READ when all headers have been read.
*/
size_t len; /* XXX try */
line = evbuffer_readln(buffer, &len, EVBUFFER_EOL_CRLF); if (line == NULL) { if (req->evcon != NULL &&
evbuffer_get_length(buffer) > req->evcon->max_headers_size) return (DATA_TOO_LONG); else return (MORE_DATA_EXPECTED);
}
if (req->evcon != NULL && len > req->evcon->max_headers_size) {
mm_free(line); return (DATA_TOO_LONG);
}
req->headers_size = len;
switch (req->kind) { case EVHTTP_REQUEST: if (evhttp_parse_request_line(req, line, len) == -1)
status = DATA_CORRUPTED; break; case EVHTTP_RESPONSE: if (evhttp_parse_response_line(req, line) == -1)
status = DATA_CORRUPTED; break; default:
status = DATA_CORRUPTED;
}
if (*line == '\0') { /* Last header - Done */
status = ALL_DATA_READ;
mm_free(line); break;
}
/* Check if this is a continuation line */ if (*line == ' ' || *line == '\t') { if (evhttp_append_to_last_header(headers, line) == -1) goto error;
mm_free(line); continue;
}
/* Processing of header lines */
svalue = line;
skey = strsep(&svalue, ":"); if (svalue == NULL) goto error;
/* If this is a request without a body, then we are done */ if (req->kind == EVHTTP_REQUEST &&
!evhttp_method_may_have_body(req->type)) {
evhttp_connection_done(evcon); return;
}
evcon->state = EVCON_READING_BODY;
xfer_enc = evhttp_find_header(req->input_headers, "Transfer-Encoding"); if (xfer_enc != NULL && evutil_ascii_strcasecmp(xfer_enc, "chunked") == 0) {
req->chunked = 1;
req->ntoread = -1;
} else { if (evhttp_get_body_length(req) == -1) {
evhttp_connection_fail_(evcon, EVREQ_HTTP_INVALID_HEADER); return;
} if (req->kind == EVHTTP_REQUEST && req->ntoread < 1) { /* An incoming request with no content-length and no
* transfer-encoding has no body. */
evhttp_connection_done(evcon); return;
}
}
/* Should we send a 100 Continue status line? */ switch (evhttp_have_expect(req, 1)) { caseCONTINUE: /* XXX It would be nice to do some sanity checking here. Does the resource exist? Should the resource accept post requests? If no, we should respond with an error. For now, just optimistically tell the client to
send their message body. */ if (req->ntoread > 0) { /* ntoread is ev_int64_t, max_body_size is ev_uint64_t */ if ((req->evcon->max_body_size <= EV_INT64_MAX) &&
(ev_uint64_t)req->ntoread > req->evcon->max_body_size) {
evhttp_lingering_fail(evcon, req); return;
}
} if (!evbuffer_get_length(bufferevent_get_input(evcon->bufev)))
evhttp_send_continue(evcon, req); break; case OTHER:
evhttp_send_error(req, HTTP_EXPECTATIONFAILED, NULL); return; case NO: break;
}
evhttp_read_body(evcon, req); /* note the request may have been freed in evhttp_read_body */
}
res = evhttp_parse_headers_(req, bufferevent_get_input(evcon->bufev)); if (res == DATA_CORRUPTED || res == DATA_TOO_LONG) { /* Error while reading, terminate */
event_debug(("%s: bad header lines on "EV_SOCK_FMT"\n",
__func__, EV_SOCK_ARG(fd)));
evhttp_connection_fail_(evcon, EVREQ_HTTP_INVALID_HEADER); return;
} elseif (res == MORE_DATA_EXPECTED) { /* Need more header lines */ return;
}
/* Callback can shut down connection with negative return value */ if (req->header_cb != NULL) { if ((*req->header_cb)(req, req->cb_arg) < 0) {
evhttp_connection_fail_(evcon, EVREQ_HTTP_EOF); return;
}
}
/* Done reading headers, do the real work */ switch (req->kind) { case EVHTTP_REQUEST:
event_debug(("%s: checking for post data on "EV_SOCK_FMT"\n",
__func__, EV_SOCK_ARG(fd)));
evhttp_get_body(evcon, req); /* note the request may have been freed in evhttp_get_body */ break;
case EVHTTP_RESPONSE: /* Start over if we got a 100 Continue response. */ if (req->response_code == 100) { struct evbuffer *output = bufferevent_get_output(evcon->bufev);
evbuffer_add_buffer(output, req->output_buffer);
evhttp_start_write_(evcon); return;
} if (!evhttp_response_needs_body(req)) {
event_debug(("%s: skipping body for code %d\n",
__func__, req->response_code));
evhttp_connection_done(evcon);
} else {
event_debug(("%s: start of read body for %s on "
EV_SOCK_FMT"\n",
__func__, req->remote_host, EV_SOCK_ARG(fd)));
evhttp_get_body(evcon, req); /* note the request may have been freed in
* evhttp_get_body */
} break;
default:
event_warnx("%s: bad header on "EV_SOCK_FMT, __func__,
EV_SOCK_ARG(fd));
evhttp_connection_fail_(evcon, EVREQ_HTTP_INVALID_HEADER); break;
} /* request may have been freed above */
}
/* * Creates a TCP connection to the specified port and executes a callback * when finished. Failure or success is indicate by the passed connection * object. * * Although this interface accepts a hostname, it is intended to take * only numeric hostnames so that non-blocking DNS resolution can * happen elsewhere.
*/
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.