flood_socket_keepalive.c (419 lines of code) (raw):

/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Originally developed by Aaron Bannert and Justin Erenkrantz, eBuilt. */ #include <apr.h> #include <apr_strings.h> #if APR_HAVE_STDLIB_H #include <stdlib.h> /* rand/strtol */ #endif #if APR_HAVE_STRING_H #include <string.h> #endif #include <assert.h> #include "config.h" #include "flood_net.h" #include "flood_net_ssl.h" #include "flood_socket_keepalive.h" #define ksock_read_socket(ksock, buf, lenaddr) \ ksock->ssl ? ssl_read_socket(ksock->s, buf, lenaddr) : \ read_socket(ksock->s, buf, lenaddr) #define ksock_write_socket(ksock, req) \ ksock->ssl ? ssl_write_socket(ksock->s, req) : \ write_socket(ksock->s, req) #define ksock_check_socket(ksock, pool) \ ksock->ssl ? ssl_check_socket(ksock->s, pool) : \ check_socket(ksock->s, pool) #define ksock_close_socket(ksock) \ ksock->ssl ? ssl_close_socket(ksock->s) : \ close_socket(ksock->s) typedef struct { void *s; apr_pollfd_t *p; int reopen_socket; /* A boolean */ int wantresponse; /* A boolean */ int ssl; /* A boolean */ method_e method; /* The method of the request. */ } keepalive_socket_t; /** * Keep-alive implementation for socket_init. */ apr_status_t keepalive_socket_init(socket_t **sock, apr_pool_t *pool) { keepalive_socket_t *new_ksock; new_ksock = (keepalive_socket_t *)apr_palloc(pool, sizeof(keepalive_socket_t)); if (new_ksock == NULL) return APR_ENOMEM; new_ksock->s = NULL; new_ksock->p = NULL; new_ksock->reopen_socket = 1; new_ksock->wantresponse = 1; new_ksock->ssl = 0; *sock = new_ksock; return APR_SUCCESS; } /** * Keep-alive implementation for begin_conn. */ apr_status_t keepalive_begin_conn(socket_t *sock, request_t *req, apr_pool_t *pool) { keepalive_socket_t *ksock = (keepalive_socket_t *)sock; if (!ksock->reopen_socket && ksock->s) { apr_status_t e; e = ksock_check_socket(ksock, pool); if (e != APR_SUCCESS) { ksock->reopen_socket = 1; } } if (ksock->reopen_socket || ksock->s == NULL) { apr_status_t rv; if (strcasecmp(req->parsed_uri->scheme, "https") == 0) { /* If we don't have SSL, error out. */ #if FLOOD_HAS_OPENSSL ksock->ssl = 1; #else return APR_ENOTIMPL; #endif } else { ksock->ssl = 0; } /* The return types are not identical, so it can't be a ternary * operation. */ if (ksock->ssl) ksock->s = ssl_open_socket(pool, req, &rv); else ksock->s = open_socket(pool, req, &rv); if (ksock->s == NULL) return rv; ksock->reopen_socket = 0; /* we just opened it */ } req->keepalive = 1; return APR_SUCCESS; } /** * Keep-alive implementation for send_req. */ apr_status_t keepalive_send_req(socket_t *sock, request_t *req, apr_pool_t *pool) { keepalive_socket_t *ksock = (keepalive_socket_t *)sock; ksock->wantresponse = req->wantresponse; ksock->method = req->method; return ksock->ssl ? ssl_write_socket(ksock->s, req) : write_socket(ksock->s, req); } static long keepalive_read_chunk_size(char *begin_chunk) { char chunk[17], *end_chunk; long chunk_length; /* FIXME: Handle chunk-extension */ end_chunk = strstr(begin_chunk, CRLF); if (end_chunk && end_chunk - begin_chunk < 16) { strncpy(chunk, begin_chunk, end_chunk - begin_chunk); chunk[end_chunk-begin_chunk] = '\0'; /* Chunks are base-16 */ chunk_length = strtol(chunk, &end_chunk, 16); if (*end_chunk == '\0') return chunk_length; } return 0; } static apr_status_t keepalive_read_chunk(response_t *resp, keepalive_socket_t *sock, int chunk_length, char **bp, int bplen) { apr_status_t status = APR_SUCCESS; int old_length = 0; if (!resp->chunk) { chunk_length = 0; } else if (chunk_length < 0) { old_length = chunk_length; chunk_length = 0; } else if (chunk_length == 0) { return status; } do { /* Sentinel value */ apr_size_t blen = 0; char *start_chunk, *end_chunk, *b; /* Always reset the b. */ b = *bp; /* Time to read the next chunk size. At this point, * we should be ready to read a CRLF followed by * a line that contains the next chunk size. */ while (!chunk_length) { /* We are reading the next chunk and see a CRLF. */ if (blen >= 1 && b[0] == '\r') { b++; blen--; if (blen >= 1 && b[0] == '\n') { b++; blen--; } else { old_length = -1; } } /* If blen is 0, we're empty so read more data. */ while (!blen) { /* Reset and read as much as we can. */ blen = bplen; b = *bp; status = ksock_read_socket(sock, b, &blen); if (status != APR_SUCCESS) { return status; } /* We got caught in the middle of a chunk last time. */ if (old_length < 0) { b -= old_length; blen += old_length; old_length = 0; } /* We are reading the next chunk and see a CRLF. */ if (blen >= 2 && b[0] == '\r' && b[1] == '\n') { b += 2; blen -= 2; } } start_chunk = b; chunk_length = keepalive_read_chunk_size(start_chunk); /* last-chunk */ if (!chunk_length) { /* See if we already read the trailer and final CRLF */ end_chunk = strstr(b, CRLF CRLF); if (!end_chunk) { /* Read as much as we can. */ blen = bplen; b = *bp; status = ksock_read_socket(sock, b, &blen); if (status != APR_SUCCESS) return status; } /* FIXME: If we add pipelining, we need to put * the remainder back so that it can be read. */ blen -= end_chunk - b + 4; return APR_SUCCESS; } /* If this fails, we're very unlikely to have read a chunk! */ end_chunk = strstr(start_chunk, CRLF) + 2; blen -= end_chunk - b; /* Oh no, we read more than one chunk this go-around! */ if (chunk_length <= blen) { b += chunk_length + (end_chunk - b); blen -= chunk_length; chunk_length = 0; } else chunk_length -= blen; } if (chunk_length > bplen) blen = bplen; else blen = chunk_length; status = ksock_read_socket(sock, b, &blen); chunk_length -= blen; } while (status == APR_SUCCESS); return APR_SUCCESS; } static apr_status_t keepalive_load_resp(response_t *resp, keepalive_socket_t *sock, apr_size_t remaining, apr_pool_t *pool) { /* Ugh, we want everything. */ int currentalloc, remain; apr_size_t i; char *cp, *op, b[MAX_DOC_LENGTH]; apr_status_t status; if (remaining > 0) { remain = 1; currentalloc = remaining + resp->rbufsize; } else { remain = 0; currentalloc = MAX_DOC_LENGTH + resp->rbufsize; } cp = apr_palloc(pool, currentalloc); memcpy(cp, resp->rbuf, resp->rbufsize); resp->rbuf = cp; cp = resp->rbuf + resp->rbufsize; do { if (!remain) i = MAX_DOC_LENGTH - 1; else { if (remaining > MAX_DOC_LENGTH - 1) i = MAX_DOC_LENGTH - 1; else i = remaining; } status = ksock_read_socket(sock, b, &i); if (resp->rbufsize + i > currentalloc) { /* You can think why this always work. */ currentalloc *= 2; op = resp->rbuf; resp->rbuf = apr_palloc(pool, currentalloc); memcpy(resp->rbuf, op, cp - op); cp = resp->rbuf + (cp - op); } memcpy(cp, b, i); resp->rbufsize += i; cp += i; remaining -= i; } while (status != APR_EGENERAL && status != APR_EOF && status != APR_TIMEUP && (!remain || remaining)); return status; } /** * Keep-alive implementation for recv_resp. */ apr_status_t keepalive_recv_resp(response_t **resp, socket_t *sock, apr_pool_t *pool) { keepalive_socket_t *ksock = (keepalive_socket_t *)sock; char *cl, *ecl, cls[17]; char *current_line; apr_size_t i; response_t *new_resp; apr_status_t status; long content_length = 0, chunk_length; const char *header; new_resp = apr_pcalloc(pool, sizeof(response_t)); new_resp->rbuftype = POOL; new_resp->rbufsize = MAX_DOC_LENGTH - 1; new_resp->rbuf = apr_palloc(pool, new_resp->rbufsize); status = ksock_read_socket(ksock, new_resp->rbuf, &new_resp->rbufsize); if (status != APR_SUCCESS && status != APR_EOF) { return status; } /* FIXME: Assume we got the full header for now. */ new_resp->headers = apr_table_make(pool, 25); current_line = new_resp->rbuf; do { char *end_of_line, *header_end, *header_key, *header_val; int line_length, key_length; end_of_line = strstr(current_line, CRLF); if (!end_of_line || end_of_line == current_line) { break; } line_length = end_of_line - current_line; header_end = memchr(current_line, ':', line_length); if (header_end) { key_length = header_end - current_line; header_key = apr_pstrmemdup(pool, current_line, key_length); header_val = apr_pstrmemdup(pool, current_line + key_length + 2, line_length - key_length - 2); apr_table_set(new_resp->headers, header_key, header_val); } current_line += line_length + sizeof(CRLF) - 1; } while((current_line - new_resp->rbuf) < new_resp->rbufsize); /* If this exists, we aren't keepalive anymore. */ header = apr_table_get(new_resp->headers, "Connection"); if (header && !strcasecmp(header, "Close")) { new_resp->keepalive = 0; } else { new_resp->keepalive = 1; } /* If we have a HEAD request, we shouldn't be receiving a body. */ if (ksock->method == HEAD) { *resp = new_resp; return APR_SUCCESS; } header = apr_table_get(new_resp->headers, "Transfer-Encoding"); if (header && !strcasecmp(header, "Chunked")) { new_resp->chunked = 1; new_resp->chunk = NULL; chunk_length = 0; /* Find where headers ended */ cl = current_line; if (cl) { /* Skip over the CRLF chars */ cl += sizeof(CRLF)-1; } /* We have a partial chunk and we aren't at the end. */ if (cl && *cl && (cl - (char*)new_resp->rbuf) < new_resp->rbufsize) { int remaining; do { if (new_resp->chunk) { /* If we have enough space to skip over the ending CRLF, * do so. */ if (chunk_length + 2 <= remaining) { new_resp->chunk += chunk_length + 2; } else { /* We got more than a chunk, but not the full CRLF. */ chunk_length = -(remaining - chunk_length); remaining = 0; break; } } else { new_resp->chunk = cl; } if ((new_resp->chunk - (char*)new_resp->rbuf) < new_resp->rbufsize && *new_resp->chunk) { char *foo; chunk_length = keepalive_read_chunk_size(new_resp->chunk); /* Search for the beginning of the chunk. */ foo = strstr(new_resp->chunk, CRLF); assert(foo); new_resp->chunk = foo + 2; remaining = new_resp->rbufsize - (int)(new_resp->chunk - (char*)new_resp->rbuf); } else { new_resp->chunk = NULL; remaining = 0; } } while (remaining > chunk_length); chunk_length -= remaining; } } else { header = apr_table_get(new_resp->headers, "Content-Length"); if (!header) { new_resp->keepalive = 0; } if (header) { cl = (char*)header; ecl = cl + strlen(cl); if (ecl && ecl - cl < 16) { strncpy(cls, cl, ecl - cl); cls[ecl-cl] = '\0'; content_length = strtol(cls, &ecl, 10); if (*ecl != '\0') new_resp->keepalive = 0; } } if (new_resp->keepalive) { /* Find where we ended */ ecl = current_line; /* We didn't get full headers. Crap. */ if (!ecl) new_resp->keepalive = 0; { ecl += sizeof(CRLF) - 1; content_length -= new_resp->rbufsize - (ecl - (char*)new_resp->rbuf); } } } if (ksock->wantresponse) { if (new_resp->keepalive) status = keepalive_load_resp(new_resp, ksock, content_length, pool); else status = keepalive_load_resp(new_resp, ksock, 0, pool); } else { char *b = apr_palloc(pool, MAX_DOC_LENGTH); if (new_resp->chunked) { status = keepalive_read_chunk(new_resp, ksock, chunk_length, &b, MAX_DOC_LENGTH - 1); } else if (new_resp->keepalive) { while (content_length && status != APR_EGENERAL && status != APR_EOF && status != APR_TIMEUP) { if (content_length > MAX_DOC_LENGTH - 1) i = MAX_DOC_LENGTH - 1; else i = content_length; status = ksock_read_socket(ksock, b, &i); content_length -= i; } } else { while (status != APR_EGENERAL && status != APR_EOF && status != APR_TIMEUP) { i = MAX_DOC_LENGTH - 1; status = ksock_read_socket(ksock, b, &i); } } } *resp = new_resp; return APR_SUCCESS; } /** * Keep-alive implementation for end_conn. */ apr_status_t keepalive_end_conn(socket_t *sock, request_t *req, response_t *resp) { keepalive_socket_t *ksock = (keepalive_socket_t *)sock; if (resp->keepalive == 0) { ksock_close_socket(ksock); ksock->reopen_socket = 1; /* we just closed it */ } return APR_SUCCESS; } apr_status_t keepalive_socket_destroy(socket_t *sock) { return APR_SUCCESS; }