blob: e7078090ba24998ff23ac988be2cae8d9da24681 [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.
* ====================================================================
*/
#include <apr_pools.h>
#include <apr_poll.h>
#include <apr_version.h>
#include <apr_portable.h>
#include <apr_strings.h>
#include "serf.h"
#include "serf_bucket_util.h"
#include "serf_private.h"
static apr_status_t clean_resp(void *data)
{
serf_request_t *request = data;
apr_pool_t *respool = request->respool;
request->respool = NULL;
/* The request's RESPOOL is being cleared. */
if (respool && request->writing >= SERF_WRITING_STARTED
&& request->writing < SERF_WRITING_FINISHED) {
/* HOUSTON, WE HAVE A PROBLEM.
We created buckets inside the pool that is now cleaned and
stored them in an aggregate that still lives on.
This happens when the application decides that it doesn't
need the connection any more and clears the pool of the
connection, of which the request pool is a subpool.
Let's ask the connection to take care of things */
serf__connection_pre_cleanup(request->conn);
}
#ifdef SERF_DEBUG_BUCKET_USE
if (respool && request->allocator) {
serf_debug__closed_conn(request->allocator);
}
#endif
/* If the response has allocated some buckets, then destroy them (since
the bucket may hold resources other than memory in RESPOOL). Also
make sure to set their fields to NULL so connection closure does
not attempt to free them again. */
if (request->resp_bkt) {
serf_bucket_destroy(request->resp_bkt);
request->resp_bkt = NULL;
}
if (request->req_bkt) {
if (request->writing == SERF_WRITING_NONE)
serf_bucket_destroy(request->req_bkt);
request->req_bkt = NULL;
}
#ifdef SERF_DEBUG_BUCKET_USE
if (respool && request->allocator) {
serf_debug__bucket_alloc_check(request->allocator);
}
#endif
request->allocator = NULL;
return APR_SUCCESS;
}
void serf__link_requests(serf_request_t **list, serf_request_t **tail,
serf_request_t *request)
{
if (*list == NULL) {
*list = request;
*tail = request;
}
else {
(*tail)->next = request;
*tail = request;
}
}
apr_status_t serf__destroy_request(serf_request_t *request)
{
serf_connection_t *conn = request->conn;
if (request->writing >= SERF_WRITING_STARTED
&& request->writing < SERF_WRITING_FINISHED) {
/* Schedule for destroy when it is safe again.
Destroying now will destroy memory of buckets that we
may still need.
*/
serf__link_requests(&conn->done_reqs, &conn->done_reqs_tail,
request);
}
else {
if (request->respool) {
apr_pool_t *pool = request->respool;
apr_pool_cleanup_run(pool, request, clean_resp);
apr_pool_destroy(pool);
}
serf_bucket_mem_free(conn->allocator, request);
}
return APR_SUCCESS;
}
apr_status_t serf__cancel_request(serf_request_t *request,
serf_request_t **list,
int notify_request)
{
/* If we haven't run setup, then we won't have a handler to call. */
if (request->handler && notify_request) {
/* We actually don't care what the handler returns.
* We have bigger matters at hand.
*/
(*request->handler)(request, NULL, request->handler_baton,
request->respool);
}
if (*list == request) {
*list = request->next;
}
else {
serf_request_t *scan = *list;
while (scan->next && scan->next != request)
scan = scan->next;
if (scan->next) {
scan->next = scan->next->next;
}
}
return serf__destroy_request(request);
}
/* Calculate the length of a linked list of requests. */
unsigned int serf__req_list_length(serf_request_t *req)
{
unsigned int length = 0;
while (req) {
length++;
req = req->next;
}
return length;
}
apr_status_t serf__setup_request(serf_request_t *request)
{
serf_connection_t *conn = request->conn;
apr_status_t status;
/* Now that we are about to serve the request, allocate a pool. */
apr_pool_create(&request->respool, conn->pool);
request->allocator = serf_bucket_allocator_create(request->respool,
NULL, NULL);
apr_pool_cleanup_register(request->respool, request,
clean_resp, apr_pool_cleanup_null);
/* Fill in the rest of the values for the request. */
status = request->setup(request, request->setup_baton,
&request->req_bkt,
&request->acceptor,
&request->acceptor_baton,
&request->handler,
&request->handler_baton,
request->respool);
return status;
}
/* A response message was received from the server, so call
the handler as specified on the original request. */
apr_status_t serf__handle_response(serf_request_t *request,
apr_pool_t *pool)
{
int consumed_response = 0;
/* Only enable the new authentication framework if the program has
* registered an authentication credential callback.
*
* This permits older Serf apps to still handle authentication
* themselves by not registering credential callbacks.
*/
if (request->conn->ctx->cred_cb) {
apr_status_t status;
status = serf__handle_auth_response(&consumed_response,
request,
request->resp_bkt,
pool);
if (SERF_BUCKET_READ_ERROR(status)) {
/* There was an error while checking the authentication headers of
the response. We need to inform the application - which
hasn't seen this response yet - of the error.
These are the possible causes of the error:
1. A communication error while reading the response status line,
headers or while discarding the response body: pass the
response unchanged to the application, it will see the same
error as serf did.
2. A 401/407 response status for a supported authn scheme that
resulted in authn failure:
Pass the response as received to the application, the response
body can contain an error description. Terminate the response
body with the AUTHN error instead of APR_EOF.
3. A 401/407 response status for a supported authn scheme that
resulted in an unknown error returned by the application in
the credentials callback (Basic/Digest):
Handle the same as 2.
4. A 2xx response status for a supported authn scheme that
resulted in authn failure:
Pass the response headers to the application. The response
body is untrusted, so we should drop it and return the AUTHN
error instead of APR_EOF.
serf__handle_auth_response will already discard the response
body, so we can handle this case the same as 2.
In summary, all these cases can be handled in the same way: call
the application's response handler with the response bucket, but
make sure that the application sees error code STATUS instead of
APR_EOF after reading the response body.
*/
serf__bucket_response_set_error_on_eof(request->resp_bkt, status);
/* Ignore the application's status code here, use the error status
from serf__handle_auth_response. */
(void)(*request->handler)(request,
request->resp_bkt,
request->handler_baton,
pool);
}
if (status)
return status;
}
if (!consumed_response) {
return (*request->handler)(request,
request->resp_bkt,
request->handler_baton,
pool);
}
return APR_SUCCESS;
}
apr_status_t
serf__provide_credentials(serf_context_t *ctx,
char **username,
char **password,
serf_request_t *request,
int code, const char *authn_type,
const char *realm,
apr_pool_t *pool)
{
serf_connection_t *conn = request->conn;
serf_request_t *authn_req = request;
apr_status_t status;
if (request->ssltunnel == 1 &&
conn->state == SERF_CONN_SETUP_SSLTUNNEL) {
/* This is a CONNECT request to set up an SSL tunnel over a proxy.
This request is created by serf, so if the proxy requires
authentication, we can't ask the application for credentials with
this request.
Solution: setup the first request created by the application on
this connection, and use that request and its handler_baton to
call back to the application. */
/* request->next will be NULL if this was the last request written */
authn_req = request->next;
if (!authn_req)
authn_req = conn->unwritten_reqs;
/* assert: app_request != NULL */
if (!authn_req)
return APR_EGENERAL;
if (!authn_req->req_bkt) {
status = serf__setup_request(authn_req);
/* If we can't setup a request, don't bother setting up the
ssl tunnel. */
if (status)
return status;
}
}
/* Ask the application. */
status = (*ctx->cred_cb)(username, password,
authn_req, authn_req->handler_baton,
code, authn_type, realm, pool);
if (status)
return status;
return APR_SUCCESS;
}
static serf_request_t *
create_request(serf_connection_t *conn,
serf_request_setup_t setup,
void *setup_baton,
int priority,
int ssltunnel)
{
serf_request_t *request;
request = serf_bucket_mem_alloc(conn->allocator, sizeof(*request));
request->conn = conn;
request->setup = setup;
request->setup_baton = setup_baton;
request->handler = NULL;
request->acceptor = NULL;
request->respool = NULL;
request->req_bkt = NULL;
request->resp_bkt = NULL;
request->priority = priority;
request->writing = SERF_WRITING_NONE;
request->ssltunnel = ssltunnel;
request->next = NULL;
request->auth_baton = NULL;
return request;
}
serf_request_t *serf_connection_request_create(
serf_connection_t *conn,
serf_request_setup_t setup,
void *setup_baton)
{
serf_request_t *request;
request = create_request(conn, setup, setup_baton,
0, /* priority */
0 /* ssl tunnel */);
/* Link the request to the end of the request chain. */
serf__link_requests(&conn->unwritten_reqs, &conn->unwritten_reqs_tail, request);
conn->nr_of_unwritten_reqs++;
/* Ensure our pollset becomes writable in context run */
serf_io__set_pollset_dirty(&conn->io);
return request;
}
static serf_request_t *
priority_request_create(serf_connection_t *conn,
int ssltunnelreq,
serf_request_setup_t setup,
void *setup_baton)
{
serf_request_t *request;
serf_request_t *iter, *prev;
request = create_request(conn, setup, setup_baton,
1, /* priority */
ssltunnelreq);
/* Link the new request after the last written request. */
iter = conn->unwritten_reqs;
prev = NULL;
/* TODO: what if a request is partially written? */
/* Find a request that has data which needs to be delivered. */
while (iter != NULL && iter->req_bkt == NULL
&& (iter->writing >= SERF_WRITING_STARTED)) {
prev = iter;
iter = iter->next;
}
/* A CONNECT request to setup an ssltunnel has absolute priority over all
other requests on the connection, so:
a. add it first to the queue
b. ensure that other priority requests are added after the CONNECT
request */
if (!request->ssltunnel) {
/* Advance to next non priority request */
while (iter != NULL && iter->priority) {
prev = iter;
iter = iter->next;
}
}
if (prev) {
request->next = iter;
prev->next = request;
} else {
request->next = iter;
conn->unwritten_reqs = request;
}
conn->nr_of_unwritten_reqs++;
/* Ensure our pollset becomes writable in context run */
serf_io__set_pollset_dirty(&conn->io);
return request;
}
serf_request_t *serf_connection_priority_request_create(
serf_connection_t *conn,
serf_request_setup_t setup,
void *setup_baton)
{
return priority_request_create(conn,
0, /* not a ssltunnel CONNECT request */
setup, setup_baton);
}
serf_request_t *serf__ssltunnel_request_create(serf_connection_t *conn,
serf_request_setup_t setup,
void *setup_baton)
{
return priority_request_create(conn,
1, /* This is a ssltunnel CONNECT request */
setup, setup_baton);
}
serf_request_t *serf__request_requeue(const serf_request_t *request)
{
/* ### in the future, maybe we could reset REQUEST and try again? */
return priority_request_create(request->conn,
request->ssltunnel,
request->setup,
request->setup_baton);
}
apr_status_t serf_request_cancel(serf_request_t *request)
{
serf_connection_t *conn = request->conn;
serf_request_t *tmp = conn->unwritten_reqs;
/* Find out which queue holds the request */
while (tmp != NULL && tmp != request)
tmp = tmp->next;
if (tmp)
return serf__cancel_request(request, &conn->unwritten_reqs, 0);
else
return serf__cancel_request(request, &conn->written_reqs, 0);
}
apr_status_t serf_request_is_written(serf_request_t *request)
{
if (request->writing >= SERF_WRITING_FINISHED)
return APR_SUCCESS;
return APR_EBUSY;
}
apr_pool_t *serf_request_get_pool(const serf_request_t *request)
{
return request->respool;
}
serf_bucket_alloc_t *serf_request_get_alloc(
const serf_request_t *request)
{
return request->allocator;
}
serf_connection_t *serf_request_get_conn(
const serf_request_t *request)
{
return request->conn;
}
void serf_request_set_handler(
serf_request_t *request,
const serf_response_handler_t handler,
const void **handler_baton)
{
request->handler = handler;
request->handler_baton = handler_baton;
}
serf_bucket_t *serf_request_bucket_request_create(
serf_request_t *request,
const char *method,
const char *uri,
serf_bucket_t *body,
serf_bucket_alloc_t *allocator)
{
serf_bucket_t *req_bkt;
serf_bucket_t *hdrs_bkt;
serf_connection_t *conn = request->conn;
serf_context_t *ctx = conn->ctx;
int tunneled;
tunneled = ctx->proxy_address
&& (strcmp(conn->host_info.scheme, "https") == 0);
req_bkt = serf_bucket_request_create(method, uri, body, allocator);
hdrs_bkt = serf_bucket_request_get_headers(req_bkt);
/* Use absolute uri's in requests to a proxy. USe relative uri's in
requests directly to a server or sent through an SSL tunnel. */
if (ctx->proxy_address && conn->host_url && !tunneled)
{
serf_bucket_request_set_root(req_bkt, conn->host_url);
}
if (conn->host_info.hostinfo)
{
serf_bucket_headers_setn(hdrs_bkt, "Host", conn->host_info.hostinfo);
}
/* Setup server authentication headers. */
serf__auth_setup_request(HOST, request, method, uri, hdrs_bkt);
/* Setup proxy authentication headers, unless we're tunneling. */
if (!tunneled)
serf__auth_setup_request(PROXY, request, method, uri, hdrs_bkt);
return req_bkt;
}