blob: c9eacc9933df64541424ee37d64f8e5c9b39e21a [file] [log] [blame]
/* 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)
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 = check_socket(ksock->s, 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 (!chunk_length) {
return status;
}
if (!resp->chunk || !*resp->chunk) {
chunk_length = 0;
}
if (chunk_length < 0) {
old_length = chunk_length;
chunk_length = 0;
}
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;
/* 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->ssl ? ssl_close_socket(ksock->s) : close_socket(ksock->s);
ksock->reopen_socket = 1; /* we just closed it */
}
return APR_SUCCESS;
}
apr_status_t keepalive_socket_destroy(socket_t *sock)
{
return APR_SUCCESS;
}