| /* 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 <assert.h> |
| |
| #include <curl/curl.h> |
| |
| #include <apr_lib.h> |
| #include <apr_strings.h> |
| #include <apr_buckets.h> |
| |
| #include "md_http.h" |
| #include "md_log.h" |
| #include "md_util.h" |
| #include "md_curl.h" |
| |
| /**************************************************************************************************/ |
| /* md_http curl implementation */ |
| |
| |
| static apr_status_t curl_status(unsigned int curl_code) |
| { |
| switch (curl_code) { |
| case CURLE_OK: return APR_SUCCESS; |
| case CURLE_UNSUPPORTED_PROTOCOL: return APR_ENOTIMPL; |
| case CURLE_NOT_BUILT_IN: return APR_ENOTIMPL; |
| case CURLE_URL_MALFORMAT: return APR_EINVAL; |
| case CURLE_COULDNT_RESOLVE_PROXY:return APR_ECONNREFUSED; |
| case CURLE_COULDNT_RESOLVE_HOST: return APR_ECONNREFUSED; |
| case CURLE_COULDNT_CONNECT: return APR_ECONNREFUSED; |
| case CURLE_REMOTE_ACCESS_DENIED: return APR_EACCES; |
| case CURLE_OUT_OF_MEMORY: return APR_ENOMEM; |
| case CURLE_OPERATION_TIMEDOUT: return APR_TIMEUP; |
| case CURLE_SSL_CONNECT_ERROR: return APR_ECONNABORTED; |
| case CURLE_AGAIN: return APR_EAGAIN; |
| default: return APR_EGENERAL; |
| } |
| } |
| |
| typedef struct { |
| CURL *curl; |
| CURLM *curlm; |
| struct curl_slist *req_hdrs; |
| md_http_response_t *response; |
| apr_status_t rv; |
| int status_fired; |
| } md_curl_internals_t; |
| |
| static size_t req_data_cb(void *data, size_t len, size_t nmemb, void *baton) |
| { |
| apr_bucket_brigade *body = baton; |
| size_t blen, read_len = 0, max_len = len * nmemb; |
| const char *bdata; |
| char *rdata = data; |
| apr_bucket *b; |
| apr_status_t rv; |
| |
| while (body && !APR_BRIGADE_EMPTY(body) && max_len > 0) { |
| b = APR_BRIGADE_FIRST(body); |
| if (APR_BUCKET_IS_METADATA(b)) { |
| if (APR_BUCKET_IS_EOS(b)) { |
| body = NULL; |
| } |
| } |
| else { |
| rv = apr_bucket_read(b, &bdata, &blen, APR_BLOCK_READ); |
| if (rv == APR_SUCCESS) { |
| if (blen > max_len) { |
| apr_bucket_split(b, max_len); |
| blen = max_len; |
| } |
| memcpy(rdata, bdata, blen); |
| read_len += blen; |
| max_len -= blen; |
| rdata += blen; |
| } |
| else { |
| body = NULL; |
| if (!APR_STATUS_IS_EOF(rv)) { |
| /* everything beside EOF is an error */ |
| read_len = CURL_READFUNC_ABORT; |
| } |
| } |
| |
| } |
| apr_bucket_delete(b); |
| } |
| |
| return read_len; |
| } |
| |
| static size_t resp_data_cb(void *data, size_t len, size_t nmemb, void *baton) |
| { |
| md_curl_internals_t *internals = baton; |
| md_http_response_t *res = internals->response; |
| size_t blen = len * nmemb; |
| apr_status_t rv; |
| |
| if (res->body) { |
| if (res->req->resp_limit) { |
| apr_off_t body_len = 0; |
| apr_brigade_length(res->body, 0, &body_len); |
| if (body_len + (apr_off_t)blen > res->req->resp_limit) { |
| return 0; /* signal curl failure */ |
| } |
| } |
| rv = apr_brigade_write(res->body, NULL, NULL, (const char *)data, blen); |
| if (rv != APR_SUCCESS) { |
| /* returning anything != blen will make CURL fail this */ |
| return 0; |
| } |
| } |
| return blen; |
| } |
| |
| static size_t header_cb(void *buffer, size_t elen, size_t nmemb, void *baton) |
| { |
| md_curl_internals_t *internals = baton; |
| md_http_response_t *res = internals->response; |
| size_t len, clen = elen * nmemb; |
| const char *name = NULL, *value = "", *b = buffer; |
| apr_size_t i; |
| |
| len = (clen && b[clen-1] == '\n')? clen-1 : clen; |
| len = (len && b[len-1] == '\r')? len-1 : len; |
| for (i = 0; i < len; ++i) { |
| if (b[i] == ':') { |
| name = apr_pstrndup(res->req->pool, b, i); |
| ++i; |
| while (i < len && b[i] == ' ') { |
| ++i; |
| } |
| if (i < len) { |
| value = apr_pstrndup(res->req->pool, b+i, len - i); |
| } |
| break; |
| } |
| } |
| |
| if (name != NULL) { |
| apr_table_add(res->headers, name, value); |
| } |
| return clen; |
| } |
| |
| typedef struct { |
| md_http_request_t *req; |
| struct curl_slist *hdrs; |
| apr_status_t rv; |
| } curlify_hdrs_ctx; |
| |
| static int curlify_headers(void *baton, const char *key, const char *value) |
| { |
| curlify_hdrs_ctx *ctx = baton; |
| const char *s; |
| |
| if (strchr(key, '\r') || strchr(key, '\n') |
| || strchr(value, '\r') || strchr(value, '\n')) { |
| ctx->rv = APR_EINVAL; |
| return 0; |
| } |
| s = apr_psprintf(ctx->req->pool, "%s: %s", key, value); |
| ctx->hdrs = curl_slist_append(ctx->hdrs, s); |
| return 1; |
| } |
| |
| /* Convert timeout values for curl. Since curl uses 0 to disable |
| * timeout, return at least 1 if the apr_time_t value is non-zero. */ |
| static long timeout_msec(apr_time_t timeout) |
| { |
| long ms = (long)apr_time_as_msec(timeout); |
| return ms? ms : (timeout? 1 : 0); |
| } |
| |
| static long timeout_sec(apr_time_t timeout) |
| { |
| long s = (long)apr_time_sec(timeout); |
| return s? s : (timeout? 1 : 0); |
| } |
| |
| static int curl_debug_log(CURL *curl, curl_infotype type, char *data, size_t size, void *baton) |
| { |
| md_http_request_t *req = baton; |
| |
| (void)curl; |
| switch (type) { |
| case CURLINFO_TEXT: |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->pool, |
| "req[%d]: info %s", req->id, apr_pstrndup(req->pool, data, size)); |
| break; |
| case CURLINFO_HEADER_OUT: |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->pool, |
| "req[%d]: header --> %s", req->id, apr_pstrndup(req->pool, data, size)); |
| break; |
| case CURLINFO_HEADER_IN: |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->pool, |
| "req[%d]: header <-- %s", req->id, apr_pstrndup(req->pool, data, size)); |
| break; |
| case CURLINFO_DATA_OUT: |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->pool, |
| "req[%d]: data --> %ld bytes", req->id, (long)size); |
| if (md_log_is_level(req->pool, MD_LOG_TRACE5)) { |
| md_data_t d; |
| const char *s; |
| md_data_init(&d, data, size); |
| md_data_to_hex(&s, 0, req->pool, &d); |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE5, 0, req->pool, |
| "req[%d]: data(hex) --> %s", req->id, s); |
| } |
| break; |
| case CURLINFO_DATA_IN: |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->pool, |
| "req[%d]: data <-- %ld bytes", req->id, (long)size); |
| if (md_log_is_level(req->pool, MD_LOG_TRACE5)) { |
| md_data_t d; |
| const char *s; |
| md_data_init(&d, data, size); |
| md_data_to_hex(&s, 0, req->pool, &d); |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE5, 0, req->pool, |
| "req[%d]: data(hex) <-- %s", req->id, s); |
| } |
| break; |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| static apr_status_t internals_setup(md_http_request_t *req) |
| { |
| md_curl_internals_t *internals; |
| CURL *curl; |
| apr_status_t rv = APR_SUCCESS; |
| |
| curl = md_http_get_impl_data(req->http); |
| if (!curl) { |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, "creating curl instance"); |
| curl = curl_easy_init(); |
| if (!curl) { |
| rv = APR_EGENERAL; |
| goto leave; |
| } |
| curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb); |
| curl_easy_setopt(curl, CURLOPT_HEADERDATA, NULL); |
| curl_easy_setopt(curl, CURLOPT_READFUNCTION, req_data_cb); |
| curl_easy_setopt(curl, CURLOPT_READDATA, NULL); |
| curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, resp_data_cb); |
| curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL); |
| } |
| else { |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, "reusing curl instance from http"); |
| } |
| |
| internals = apr_pcalloc(req->pool, sizeof(*internals)); |
| internals->curl = curl; |
| |
| internals->response = apr_pcalloc(req->pool, sizeof(md_http_response_t)); |
| internals->response->req = req; |
| internals->response->status = 400; |
| internals->response->headers = apr_table_make(req->pool, 5); |
| internals->response->body = apr_brigade_create(req->pool, req->bucket_alloc); |
| |
| curl_easy_setopt(curl, CURLOPT_URL, req->url); |
| if (!apr_strnatcasecmp("GET", req->method)) { |
| /* nop */ |
| } |
| else if (!apr_strnatcasecmp("HEAD", req->method)) { |
| curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); |
| } |
| else if (!apr_strnatcasecmp("POST", req->method)) { |
| curl_easy_setopt(curl, CURLOPT_POST, 1L); |
| } |
| else { |
| curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, req->method); |
| } |
| curl_easy_setopt(curl, CURLOPT_HEADERDATA, internals); |
| curl_easy_setopt(curl, CURLOPT_READDATA, req->body); |
| curl_easy_setopt(curl, CURLOPT_WRITEDATA, internals); |
| |
| if (req->timeout.overall > 0) { |
| curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout_msec(req->timeout.overall)); |
| } |
| if (req->timeout.connect > 0) { |
| curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, timeout_msec(req->timeout.connect)); |
| } |
| if (req->timeout.stalled > 0) { |
| curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, req->timeout.stall_bytes_per_sec); |
| curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, timeout_sec(req->timeout.stalled)); |
| } |
| if (req->ca_file) { |
| curl_easy_setopt(curl, CURLOPT_CAINFO, req->ca_file); |
| } |
| if (req->unix_socket_path) { |
| curl_easy_setopt(curl, CURLOPT_UNIX_SOCKET_PATH, req->unix_socket_path); |
| } |
| |
| if (req->body_len >= 0) { |
| /* set the Content-Length */ |
| curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)req->body_len); |
| curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)req->body_len); |
| } |
| |
| if (req->user_agent) { |
| curl_easy_setopt(curl, CURLOPT_USERAGENT, req->user_agent); |
| } |
| if (req->proxy_url) { |
| curl_easy_setopt(curl, CURLOPT_PROXY, req->proxy_url); |
| } |
| if (!apr_is_empty_table(req->headers)) { |
| curlify_hdrs_ctx ctx; |
| |
| ctx.req = req; |
| ctx.hdrs = NULL; |
| ctx.rv = APR_SUCCESS; |
| apr_table_do(curlify_headers, &ctx, req->headers, NULL); |
| internals->req_hdrs = ctx.hdrs; |
| if (ctx.rv == APR_SUCCESS) { |
| curl_easy_setopt(curl, CURLOPT_HTTPHEADER, internals->req_hdrs); |
| } |
| } |
| |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, |
| "req[%d]: %s %s", req->id, req->method, req->url); |
| |
| if (md_log_is_level(req->pool, MD_LOG_TRACE4)) { |
| curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); |
| curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_debug_log); |
| curl_easy_setopt(curl, CURLOPT_DEBUGDATA, req); |
| } |
| |
| leave: |
| req->internals = (APR_SUCCESS == rv)? internals : NULL; |
| return rv; |
| } |
| |
| static apr_status_t update_status(md_http_request_t *req) |
| { |
| md_curl_internals_t *internals = req->internals; |
| long l; |
| apr_status_t rv = APR_SUCCESS; |
| |
| if (internals) { |
| rv = curl_status(curl_easy_getinfo(internals->curl, CURLINFO_RESPONSE_CODE, &l)); |
| if (APR_SUCCESS == rv) { |
| internals->response->status = (int)l; |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, req->pool, |
| "req[%d]: http status is %d", |
| req->id, internals->response->status); |
| } |
| } |
| return rv; |
| } |
| |
| static void fire_status(md_http_request_t *req, apr_status_t rv) |
| { |
| md_curl_internals_t *internals = req->internals; |
| |
| if (internals && !internals->status_fired) { |
| internals->status_fired = 1; |
| |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, req->pool, |
| "req[%d] fire callbacks", req->id); |
| if ((APR_SUCCESS == rv) && req->cb.on_response) { |
| rv = req->cb.on_response(internals->response, req->cb.on_response_data); |
| } |
| |
| internals->rv = rv; |
| if (req->cb.on_status) { |
| req->cb.on_status(req, rv, req->cb.on_status_data); |
| } |
| } |
| } |
| |
| static apr_status_t md_curl_perform(md_http_request_t *req) |
| { |
| apr_status_t rv = APR_SUCCESS; |
| CURLcode curle; |
| md_curl_internals_t *internals; |
| long l; |
| |
| if (APR_SUCCESS != (rv = internals_setup(req))) goto leave; |
| internals = req->internals; |
| |
| curle = curl_easy_perform(internals->curl); |
| |
| rv = curl_status(curle); |
| if (APR_SUCCESS != rv) { |
| md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, req->pool, |
| "request failed(%d): %s", curle, curl_easy_strerror(curle)); |
| goto leave; |
| } |
| |
| rv = curl_status(curl_easy_getinfo(internals->curl, CURLINFO_RESPONSE_CODE, &l)); |
| if (APR_SUCCESS == rv) { |
| internals->response->status = (int)l; |
| } |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, req->pool, "request <-- %d", |
| internals->response->status); |
| |
| if (req->cb.on_response) { |
| rv = req->cb.on_response(internals->response, req->cb.on_response_data); |
| req->cb.on_response = NULL; |
| } |
| |
| leave: |
| fire_status(req, rv); |
| md_http_req_destroy(req); |
| return rv; |
| } |
| |
| static md_http_request_t *find_curl_request(apr_array_header_t *requests, CURL *curl) |
| { |
| md_http_request_t *req; |
| md_curl_internals_t *internals; |
| int i; |
| |
| for (i = 0; i < requests->nelts; ++i) { |
| req = APR_ARRAY_IDX(requests, i, md_http_request_t*); |
| internals = req->internals; |
| if (internals && internals->curl == curl) { |
| return req; |
| } |
| } |
| return NULL; |
| } |
| |
| static void add_to_curlm(md_http_request_t *req, CURLM *curlm) |
| { |
| md_curl_internals_t *internals = req->internals; |
| |
| assert(curlm); |
| assert(internals); |
| if (internals->curlm == NULL) { |
| internals->curlm = curlm; |
| } |
| assert(internals->curlm == curlm); |
| curl_multi_add_handle(curlm, internals->curl); |
| } |
| |
| static void remove_from_curlm_and_destroy(md_http_request_t *req, CURLM *curlm) |
| { |
| md_curl_internals_t *internals = req->internals; |
| |
| assert(curlm); |
| assert(internals); |
| assert(internals->curlm == curlm); |
| curl_multi_remove_handle(curlm, internals->curl); |
| internals->curlm = NULL; |
| md_http_req_destroy(req); |
| } |
| |
| static apr_status_t md_curl_multi_perform(md_http_t *http, apr_pool_t *p, |
| md_http_next_req *nextreq, void *baton) |
| { |
| md_http_t *sub_http; |
| md_http_request_t *req; |
| CURLM *curlm = NULL; |
| CURLMcode mc; |
| struct CURLMsg *curlmsg; |
| apr_array_header_t *http_spares; |
| apr_array_header_t *requests; |
| int i, running, numfds, slowdown, msgcount; |
| apr_status_t rv; |
| |
| http_spares = apr_array_make(p, 10, sizeof(md_http_t*)); |
| requests = apr_array_make(p, 10, sizeof(md_http_request_t*)); |
| curlm = curl_multi_init(); |
| if (!curlm) { |
| rv = APR_ENOMEM; |
| goto leave; |
| } |
| |
| running = 1; |
| slowdown = 0; |
| while(1) { |
| while (1) { |
| /* fetch as many requests as nextreq gives us */ |
| if (http_spares->nelts > 0) { |
| sub_http = *(md_http_t **)(apr_array_pop(http_spares)); |
| } |
| else { |
| rv = md_http_clone(&sub_http, p, http); |
| if (APR_SUCCESS != rv) { |
| md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, |
| "multi_perform[%d reqs]: setup failed", requests->nelts); |
| goto leave; |
| } |
| } |
| |
| rv = nextreq(&req, baton, sub_http, requests->nelts); |
| if (APR_STATUS_IS_ENOENT(rv)) { |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, |
| "multi_perform[%d reqs]: no more requests", requests->nelts); |
| if (!requests->nelts) { |
| goto leave; |
| } |
| break; |
| } |
| else if (APR_SUCCESS != rv) { |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, |
| "multi_perform[%d reqs]: nextreq() failed", requests->nelts); |
| APR_ARRAY_PUSH(http_spares, md_http_t*) = sub_http; |
| goto leave; |
| } |
| |
| if (APR_SUCCESS != (rv = internals_setup(req))) { |
| if (req->cb.on_status) req->cb.on_status(req, rv, req->cb.on_status_data); |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, |
| "multi_perform[%d reqs]: setup failed", requests->nelts); |
| APR_ARRAY_PUSH(http_spares, md_http_t*) = sub_http; |
| goto leave; |
| } |
| |
| APR_ARRAY_PUSH(requests, md_http_request_t*) = req; |
| add_to_curlm(req, curlm); |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, |
| "multi_perform[%d reqs]: added request", requests->nelts); |
| } |
| |
| mc = curl_multi_perform(curlm, &running); |
| if (CURLM_OK == mc) { |
| mc = curl_multi_wait(curlm, NULL, 0, 1000, &numfds); |
| if (numfds) slowdown = 0; |
| } |
| if (CURLM_OK != mc) { |
| rv = APR_ECONNABORTED; |
| md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, |
| "multi_perform[%d reqs] failed(%d): %s", |
| requests->nelts, mc, curl_multi_strerror(mc)); |
| goto leave; |
| } |
| if (!numfds) { |
| /* no activity on any connection, timeout */ |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, |
| "multi_perform[%d reqs]: slowdown %d", requests->nelts, slowdown); |
| if (slowdown) apr_sleep(apr_time_from_msec(100)); |
| ++slowdown; |
| } |
| |
| /* process status messages, e.g. that a request is done */ |
| while (running < requests->nelts) { |
| curlmsg = curl_multi_info_read(curlm, &msgcount); |
| if (!curlmsg) break; |
| if (curlmsg->msg == CURLMSG_DONE) { |
| req = find_curl_request(requests, curlmsg->easy_handle); |
| if (req) { |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p, |
| "multi_perform[%d reqs]: req[%d] done", |
| requests->nelts, req->id); |
| update_status(req); |
| fire_status(req, curl_status(curlmsg->data.result)); |
| md_array_remove(requests, req); |
| sub_http = req->http; |
| APR_ARRAY_PUSH(http_spares, md_http_t*) = sub_http; |
| remove_from_curlm_and_destroy(req, curlm); |
| } |
| else { |
| md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, |
| "multi_perform[%d reqs]: req done, but not found by handle", |
| requests->nelts); |
| } |
| } |
| } |
| }; |
| |
| leave: |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, |
| "multi_perform[%d reqs]: leaving", requests->nelts); |
| for (i = 0; i < requests->nelts; ++i) { |
| req = APR_ARRAY_IDX(requests, i, md_http_request_t*); |
| fire_status(req, APR_SUCCESS); |
| sub_http = req->http; |
| APR_ARRAY_PUSH(http_spares, md_http_t*) = sub_http; |
| remove_from_curlm_and_destroy(req, curlm); |
| } |
| if (curlm) curl_multi_cleanup(curlm); |
| return rv; |
| } |
| |
| static int initialized; |
| |
| static apr_status_t md_curl_init(void) { |
| if (!initialized) { |
| initialized = 1; |
| curl_global_init(CURL_GLOBAL_DEFAULT); |
| } |
| return APR_SUCCESS; |
| } |
| |
| static void md_curl_req_cleanup(md_http_request_t *req) |
| { |
| md_curl_internals_t *internals = req->internals; |
| if (internals) { |
| if (internals->curl) { |
| CURL *curl = md_http_get_impl_data(req->http); |
| if (curl == internals->curl) { |
| /* NOP: we have this curl at the md_http_t already */ |
| } |
| else if (!curl) { |
| /* no curl at the md_http_t yet, install this one */ |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, "register curl instance at http"); |
| md_http_set_impl_data(req->http, internals->curl); |
| } |
| else { |
| /* There already is a curl at the md_http_t and it's not this one. */ |
| curl_easy_cleanup(internals->curl); |
| } |
| } |
| if (internals->req_hdrs) curl_slist_free_all(internals->req_hdrs); |
| req->internals = NULL; |
| } |
| } |
| |
| static void md_curl_cleanup(md_http_t *http, apr_pool_t *pool) |
| { |
| CURL *curl; |
| |
| curl = md_http_get_impl_data(http); |
| if (curl) { |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, pool, "cleanup curl instance"); |
| md_http_set_impl_data(http, NULL); |
| curl_easy_cleanup(curl); |
| } |
| } |
| |
| static md_http_impl_t impl = { |
| md_curl_init, |
| md_curl_req_cleanup, |
| md_curl_perform, |
| md_curl_multi_perform, |
| md_curl_cleanup, |
| }; |
| |
| md_http_impl_t * md_curl_get_impl(apr_pool_t *p) |
| { |
| /* trigger early global curl init, before we are down a rabbit hole */ |
| (void)p; |
| md_curl_init(); |
| return &impl; |
| } |