Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Apache/modules/md/   (Apache Software Stiftung Version 2.4.65©)  Datei vom 7.6.2025 mit Größe 22 kB image not shown  

Quelle  md_curl.c   Sprache: C

 
/* 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;
    long ssl_options = 0;

    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);
        /* for a custom CA, allow certificates checking to ignore the
         * Schannel error CRYPT_E_NO_REVOCATION_CHECK (could be a missing OCSP
         * responder URL in the certs???). See issue #361 */

#ifdef CURLSSLOPT_NO_REVOKE
        ssl_options |= CURLSSLOPT_NO_REVOKE;
#endif
    }
    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);
    }

    if (ssl_options)
        curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, ssl_options);

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

87%


¤ Dauer der Verarbeitung: 0.24 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.