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