| /* 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; |
| } |