Author: brane
Date: Thu Jan 1 18:45:34 2026
New Revision: 1931047
Log:
Added error callback infrastructure.
This adds three levels and four kinds of callbacks for reporting errors
from Serf: global, context-specific and (incoming or outgoing) connection
-specific. Request and response code will use their their connection's
callback, but add extra flags to indicate the source of the error message.
The SSL code in ssl_buckets.c uses an error context that callers can (or
rather "will be able to") define so that error messages get sent to
the appropriate, caller-specific callback. This part is not yet implemented
because it requires revising some of our SSL APIs.
* CMakeLists.txt: Check if <unistd.h> is available, used by tests.
(SOURCES): Add the error_callbacks.c file.
* SConstruct: Check for <unistd.h>, as above.
* serf.h: Add public error callback prototypes and constants.
Too many of them to list here individually.
* serf_bucket_types.h
(serf_ssl_error_cb_set, serf_ssl_error_cb_t): Removed, obsolete.
* serf_private.h: Add private helpers for sending error messages to
callbacks and the ssl_context infrastructure for handling errors.
(serf_context_t): Add error_callback and error_callback_baton.
(serf_incoming_t): Likewise.
(serf_connection_t): Here, too.
* buckets/ssl_buckets.c:
Update all calls to the removed ssl-specific error callback to use
the new dispatch_ssl_error.
(serf_ssl_context_t) Remove error_callback and error_baton.
(global_error_ctx): New fallback error context, sends to global callback.
(dispatch_ssl_error): New, calls an error context's dispatcher.
(log_ssl_error): Use dispatch_ssl_error().
* src/error_callbacks.c: New file. Implements all the private helpers
declared in serf_private.h, and also:
(serf_global_error_callback_set): Implement here.
* src/context.c
(serf_context_error_callback_set): Implement here.
* src/incoming.c
(serf_incoming_error_callback_set): Implement here.
* src/outgoing.c
(serf_connection_error_callback_set): Implement here.
* test/test_util.c
(isatty): Import from headers if available, otherwise fake it.
(test_error_callback): New, an error callback for the test suite.
(setup_test_context): Register test_error_callback in verbose mode.
Added:
serf/trunk/src/error_callbacks.c
Modified:
serf/trunk/CMakeLists.txt
serf/trunk/SConstruct
serf/trunk/buckets/ssl_buckets.c
serf/trunk/serf.h
serf/trunk/serf_bucket_types.h
serf/trunk/serf_private.h
serf/trunk/src/context.c
serf/trunk/src/incoming.c
serf/trunk/src/outgoing.c
serf/trunk/test/test_util.c
Modified: serf/trunk/CMakeLists.txt
==============================================================================
--- serf/trunk/CMakeLists.txt Thu Jan 1 17:33:48 2026 (r1931046)
+++ serf/trunk/CMakeLists.txt Thu Jan 1 18:45:34 2026 (r1931047)
@@ -137,6 +137,7 @@ list(APPEND EXPORTS_BLACKLIST
list(APPEND SOURCES
"src/config_store.c"
"src/context.c"
+ "src/error_callbacks.c"
"src/deprecated.c"
"src/incoming.c"
"src/inet_pton.c"
@@ -393,6 +394,7 @@ CheckFunction("SSL_library_init" "" "SER
"openssl/ssl.h" "${OPENSSL_INCLUDE_DIR}"
${OPENSSL_LIBRARIES} ${SERF_STANDARD_LIBRARIES})
CheckHeader("stdbool.h" "HAVE_STDBOOL_H=1")
+CheckHeader("unistd.h" "HAVE_UNISTD_H=1")
CheckType("OSSL_HANDSHAKE_STATE" "openssl/ssl.h"
"SERF_HAVE_OSSL_HANDSHAKE_STATE" ${OPENSSL_INCLUDE_DIR})
if(Brotli_FOUND)
CheckType("BrotliDecoderResult" "brotli/decode.h"
"SERF_HAVE_BROTLI_DECODER_RESULT" ${BROTLI_INCLUDE_DIR})
Modified: serf/trunk/SConstruct
==============================================================================
--- serf/trunk/SConstruct Thu Jan 1 17:33:48 2026 (r1931046)
+++ serf/trunk/SConstruct Thu Jan 1 18:45:34 2026 (r1931047)
@@ -727,6 +727,8 @@ if CALLOUT_OKAY:
### some configuration stuffs
if conf.CheckCHeader('stdbool.h'):
env.Append(CPPDEFINES=['HAVE_STDBOOL_H'])
+ if conf.CheckCHeader('unistd.h'):
+ env.Append(CPPDEFINES=['HAVE_UNISTD_H'])
env = conf.Finish()
Modified: serf/trunk/buckets/ssl_buckets.c
==============================================================================
--- serf/trunk/buckets/ssl_buckets.c Thu Jan 1 17:33:48 2026
(r1931046)
+++ serf/trunk/buckets/ssl_buckets.c Thu Jan 1 18:45:34 2026
(r1931047)
@@ -206,10 +206,6 @@ struct serf_ssl_context_t {
X509 *cached_cert;
EVP_PKEY *cached_cert_pw;
- /* Error callback */
- serf_ssl_error_cb_t error_callback;
- void *error_baton;
-
apr_status_t pending_err;
/* Status of a fatal error, returned on subsequent encrypt or decrypt
@@ -229,6 +225,31 @@ struct serf_ssl_context_t {
serf_config_t *config;
};
+/* The fallback SSL error context which logs to the global error callback. */
+static const serf__ssl_error_ctx_t global_error_ctx = {
+ serf__global_ssl_error, /* dispatcher */
+ NULL /* baton */
+};
+
+static apr_status_t dispatch_ssl_error(const serf__ssl_error_ctx_t *err_ctx,
+ apr_status_t status,
+ const char *message)
+{
+ return err_ctx->dispatch(err_ctx->baton, status, message);
+}
+
+static void log_ssl_error(const serf__ssl_error_ctx_t *err_ctx,
+ apr_status_t status)
+{
+ unsigned long err;
+
+ while ((err = ERR_get_error())) {
+ char ebuf[256];
+ ERR_error_string_n(err, ebuf, sizeof(ebuf));
+ dispatch_ssl_error(err_ctx, status, ebuf);
+ }
+}
+
typedef struct ssl_context_t {
/* The bucket-independent ssl context that this bucket is associated with
*/
serf_ssl_context_t *ssl_ctx;
@@ -355,21 +376,6 @@ detect_renegotiate(const SSL *s, int whe
}
}
-static void log_ssl_error(serf_ssl_context_t *ctx)
-{
- unsigned long err;
-
- while ((err = ERR_get_error())) {
-
- if (err && ctx->error_callback) {
- char ebuf[256];
- ERR_error_string_n(err, ebuf, sizeof(ebuf));
- ctx->error_callback(ctx->error_baton, ctx->fatal_err, ebuf);
- }
-
- }
-}
-
static void bio_set_data(BIO *bio, void *data)
{
#ifndef SERF_NO_SSL_BIO_WRAPPERS
@@ -1096,7 +1102,7 @@ static apr_status_t status_from_ssl_erro
ctx->fatal_err = SERF_ERROR_SSL_COMM_FAILED;
status = ctx->fatal_err;
- log_ssl_error(ctx);
+ log_ssl_error(&global_error_ctx, status);
}
break;
@@ -1110,7 +1116,7 @@ static apr_status_t status_from_ssl_erro
default:
status = ctx->fatal_err = SERF_ERROR_SSL_COMM_FAILED;
- log_ssl_error(ctx);
+ log_ssl_error(&global_error_ctx, status);
break;
}
@@ -1217,7 +1223,7 @@ static apr_status_t ssl_decrypt(void *ba
} else {
/* A fatal error occurred. */
ctx->fatal_err = status = SERF_ERROR_SSL_COMM_FAILED;
- log_ssl_error(ctx);
+ log_ssl_error(&global_error_ctx, status);
}
} else {
*len = ssl_len;
@@ -1675,14 +1681,12 @@ static int ssl_need_client_cert(SSL *ssl
store = OSSL_STORE_open(cert_uri, ui_method, ctx, NULL, NULL);
if (!store) {
+ char ebuf[1024];
+ ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED;
+ apr_snprintf(ebuf, sizeof(ebuf), "could not open URI: %s",
cert_uri);
+ dispatch_ssl_error(&global_error_ctx, ctx->fatal_err, ebuf);
- if (ctx->error_callback) {
- char ebuf[1024];
- ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED;
- apr_snprintf(ebuf, sizeof(ebuf), "could not open URI: %s",
cert_uri);
- ctx->error_callback(ctx->error_baton, ctx->fatal_err, ebuf);
- }
-
+ log_ssl_error(&global_error_ctx, ctx->fatal_err);
break;
}
@@ -1697,14 +1701,12 @@ static int ssl_need_client_cert(SSL *ssl
}
if (!info) {
+ char ebuf[1024];
+ ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED;
+ apr_snprintf(ebuf, sizeof(ebuf), "could not read URI: %s",
cert_uri);
+ dispatch_ssl_error(&global_error_ctx, ctx->fatal_err, ebuf);
- if (ctx->error_callback) {
- char ebuf[1024];
- ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED;
- apr_snprintf(ebuf, sizeof(ebuf), "could not read URI: %s",
cert_uri);
- ctx->error_callback(ctx->error_baton, ctx->fatal_err,
ebuf);
- }
-
+ log_ssl_error(&global_error_ctx, ctx->fatal_err);
break;
}
@@ -1824,8 +1826,7 @@ static int ssl_need_client_cert(SSL *ssl
UI_destroy_method(ui_method);
if (ERR_peek_error()) {
- log_ssl_error(ctx);
-
+ log_ssl_error(&global_error_ctx, ctx->fatal_err);
return -1;
}
@@ -1888,13 +1889,13 @@ static int ssl_need_client_cert(SSL *ssl
ctx->pool);
if (status) {
- if (ctx->error_callback) {
- char ebuf[1024];
- apr_snprintf(ebuf, sizeof(ebuf), "could not open PKCS12: %s",
cert_path);
- ctx->error_callback(ctx->error_baton, ctx->fatal_err, ebuf);
- apr_strerror(status, ebuf, sizeof(ebuf));
- ctx->error_callback(ctx->error_baton, ctx->fatal_err, ebuf);
- }
+ char ebuf[1024];
+ apr_snprintf(ebuf, sizeof(ebuf), "could not open PKCS12: %s",
cert_path);
+ dispatch_ssl_error(&global_error_ctx, status, ebuf);
+ apr_strerror(status, ebuf, sizeof(ebuf));
+ dispatch_ssl_error(&global_error_ctx, status, ebuf);
+
+ ctx->fatal_err = status;
return -1;
}
@@ -1926,10 +1927,12 @@ static int ssl_need_client_cert(SSL *ssl
return 1;
}
else {
+ char ebuf[1024];
unsigned long err = ERR_get_error();
ERR_clear_error();
if (ERR_GET_LIB(err) == ERR_LIB_PKCS12 &&
- ERR_GET_REASON(err) == PKCS12_R_MAC_VERIFY_FAILURE) {
+ ERR_GET_REASON(err) == PKCS12_R_MAC_VERIFY_FAILURE)
+ {
if (ctx->cert_pw_callback) {
const char *password;
@@ -1973,15 +1976,11 @@ static int ssl_need_client_cert(SSL *ssl
return 1;
}
else {
+ ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED;
+ apr_snprintf(ebuf, sizeof(ebuf), "could not parse
PKCS12: %s", cert_path);
+ dispatch_ssl_error(&global_error_ctx,
ctx->fatal_err, ebuf);
- if (ctx->error_callback) {
- char ebuf[1024];
- ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED;
- apr_snprintf(ebuf, sizeof(ebuf), "could not
parse PKCS12: %s", cert_path);
- ctx->error_callback(ctx->error_baton,
ctx->fatal_err, ebuf);
- }
-
- log_ssl_error(ctx);
+ log_ssl_error(&global_error_ctx, ctx->fatal_err);
return -1;
}
}
@@ -1989,28 +1988,21 @@ static int ssl_need_client_cert(SSL *ssl
PKCS12_free(p12);
bio_meth_free(biom);
- if (ctx->error_callback) {
- char ebuf[1024];
- ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED;
- apr_snprintf(ebuf, sizeof(ebuf), "PKCS12 needs a password:
%s", cert_path);
- ctx->error_callback(ctx->error_baton, ctx->fatal_err,
ebuf);
- }
+ ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED;
+ apr_snprintf(ebuf, sizeof(ebuf), "PKCS12 needs a password:
%s", cert_path);
+ dispatch_ssl_error(&global_error_ctx, ctx->fatal_err, ebuf);
- log_ssl_error(ctx);
+ log_ssl_error(&global_error_ctx, ctx->fatal_err);
return -1;
}
else {
PKCS12_free(p12);
bio_meth_free(biom);
- if (ctx->error_callback) {
- char ebuf[1024];
- ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED;
- apr_snprintf(ebuf, sizeof(ebuf), "could not parse PKCS12:
%s", cert_path);
- ctx->error_callback(ctx->error_baton, ctx->fatal_err,
ebuf);
- }
+ ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED;
+ dispatch_ssl_error(&global_error_ctx, ctx->fatal_err, ebuf);
- log_ssl_error(ctx);
+ log_ssl_error(&global_error_ctx, ctx->fatal_err);
return -1;
}
}
@@ -2088,15 +2080,6 @@ void serf_ssl_server_cert_chain_callback
context->server_cert_userdata = data;
}
-void serf_ssl_error_cb_set(
- serf_ssl_context_t *context,
- serf_ssl_error_cb_t callback,
- void *baton)
-{
- context->error_callback = callback;
- context->error_baton = baton;
-}
-
static int ssl_new_session(SSL *ssl, SSL_SESSION *session)
{
serf_ssl_context_t *ctx = SSL_get_app_data(ssl);
@@ -2162,9 +2145,6 @@ static serf_ssl_context_t *ssl_init_cont
ssl_ctx->protocol_callback = NULL;
ssl_ctx->protocol_userdata = NULL;
- ssl_ctx->error_callback = NULL;
- ssl_ctx->error_baton = NULL;
-
SSL_CTX_set_verify(ssl_ctx->ctx, SSL_VERIFY_PEER,
validate_server_certificate);
SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_ALL);
@@ -2410,14 +2390,10 @@ apr_status_t serf_ssl_load_cert_file(
return APR_SUCCESS;
}
-#if 0
- else {
- /* If we'd have had a serf context *, we could have used serf logging
*/
- ERR_print_errors_fp(stderr);
- }
-#endif
- return SERF_ERROR_SSL_CERT_FAILED;
+ status = SERF_ERROR_SSL_CERT_FAILED;
+ log_ssl_error(&global_error_ctx, status);
+ return status;
}
@@ -2479,7 +2455,7 @@ apr_status_t serf_ssl_add_crl_from_file(
result = X509_STORE_add_crl(store, crl);
if (!result) {
ssl_ctx->fatal_err = status = SERF_ERROR_SSL_CERT_FAILED;
- log_ssl_error(ssl_ctx);
+ log_ssl_error(&global_error_ctx, status);
return status;
}
Modified: serf/trunk/serf.h
==============================================================================
--- serf/trunk/serf.h Thu Jan 1 17:33:48 2026 (r1931046)
+++ serf/trunk/serf.h Thu Jan 1 18:45:34 2026 (r1931047)
@@ -185,6 +185,113 @@ typedef struct serf_config_t serf_config
*/
const char *serf_error_string(apr_status_t errcode);
+/**
+ * The source of an error callback invocation.
+ *
+ * @since New in 1.5.
+ */
+/* Bit masks for error sources. */
+#define SERF_ERROR_CB_MASK 0x00ff
+#define SERF_ERROR_CB_GLOBAL 0x0001
+#define SERF_ERROR_CB_CONTEXT 0x0002
+#define SERF_ERROR_CB_OUTGOING 0x0004
+#define SERF_ERROR_CB_INCOMING 0x0008
+#define SERF_ERROR_CB_REQUEST 0x0010
+#define SERF_ERROR_CB_RESPONSE 0x0020
+
+/* The following flag can be bitwise-combined with any of the above
+ values to indicate that the message originated an SSL context. */
+#define SERF_ERROR_CB_SSL_CONTEXT 0x0100
+
+/**
+ * A callback that, when set, will be called for out-of-band error reporting.
+ *
+ * A callback can be set on any of three levels: globally, for the context, or
+ * the outgoing or incoming connection. Incoming and outgoing requests and
+ * responses do not have their own error handlers, but indicate with the source
+ * flags where the message originated. The default implementations will will
+ * send messages up this hierarchy until a custom callback is found, or the
+ * default global callback drops the message to the floor. This is the error
+ * callback hierarchy:
+ * ```
+ * Level Registration function
+ *
+ * Global serf_error_callback_set()
+ * Context serf_context_error_callback_set()
+ * Connection serf_connection_error_callback_set()
+ * Incoming serf_incoming_error_callback_set()
+ * ```
+ * In addition, any of those handlers can be called from within an SSL
+ * processing context, which is indicated by the flag on the message source.
+ *
+ * The @a baton is the object provided to the callback registration, and
+ * @a source is one of the @c SERF_ERROR_CB_* values, above.
+ *
+ * The @a message lasts only as long as the callback invocation. The caller
+ * must make a copy of the message it it wants to keep it for longer.
+ *
+ * It is possible that for a given error multiple strings will be returned
+ * in multiple callbacks. The caller may choose to handle all strings, or
+ * may choose to ignore all strings but the last most detailed one.
+ *
+ * @since New in 1.5.
+ */
+typedef apr_status_t (*serf_error_cb_t)(
+ void *baton,
+ unsigned source,
+ apr_status_t status,
+ const char *message);
+
+/**
+ * Register the global error @a callback, replacing any previous version.
+ *
+ * @note This function is NOT thread-safe, and calls to the callback are not
+ * serialized. Users are responsible for making the registration and
+ * the callback implementation safe for their application.
+ *
+ * @since New in 1.5.
+ */
+void serf_global_error_callback_set(
+ serf_error_cb_t callback,
+ void *baton);
+
+/**
+ * Register the context-specific error callback.
+ *
+ * Like serf_error_callback_set() except that it affects the given
+ * context @a ctx and, since contexts may not be accessed from multiple
+ * threads, serialization is not a concern.
+ *
+ * @since New in 1.5.
+ */
+void serf_context_error_callback_set(
+ serf_context_t *ctx,
+ serf_error_cb_t callback,
+ void *baton);
+
+/**
+ * Register the connection-specific error callback.
+ *
+ * Like serf_context_error_callback_set() but for connections.
+ *
+ * @since New in 1.5.
+ */
+void serf_connection_error_callback_set(
+ serf_connection_t *conn,
+ serf_error_cb_t callback,
+ void *baton);
+
+/**
+ * Register the incoming-connection-specific error callback.
+ *
+ * Like serf_context_error_callback_set() but for incoming connections.
+ *
+ * @since New in 1.5.
+ */
+void serf_incoming_error_callback_set(
+ serf_incoming_t *client,
+ serf_error_cb_t callback,
+ void *baton);
/**
* Create a new context for serf operations.
Modified: serf/trunk/serf_bucket_types.h
==============================================================================
--- serf/trunk/serf_bucket_types.h Thu Jan 1 17:33:48 2026
(r1931046)
+++ serf/trunk/serf_bucket_types.h Thu Jan 1 18:45:34 2026
(r1931047)
@@ -687,33 +687,6 @@ void serf_ssl_server_cert_chain_callback
void *data);
/**
- * Callback type for detailed TLS error strings. This callback will be fired
- * every time the underlying crypto library encounters an error. The message
- * lasts only as long as the callback, if the caller wants to set aside the
- * message for later use, a copy must be made.
- *
- * It is possible that for a given error multiple strings will be returned
- * in multiple callbacks. The caller may choose to handle all strings, or
- * may choose to ignore all strings but the last most detailed one.
- */
-typedef apr_status_t (*serf_ssl_error_cb_t)(
- void *baton,
- apr_status_t status,
- const char *message);
-
-/**
- * Set a callback to return any detailed certificate error from the underlying
- * cryptographic library.
- *
- * The callback is associated with the context, however the choice of baton
- * will depend on the needs of the caller.
- */
-void serf_ssl_error_cb_set(
- serf_ssl_context_t *context,
- serf_ssl_error_cb_t callback,
- void *baton);
-
-/**
* Use the default root CA certificates as included with the OpenSSL library.
*/
apr_status_t serf_ssl_use_default_certificates(
Modified: serf/trunk/serf_private.h
==============================================================================
--- serf/trunk/serf_private.h Thu Jan 1 17:33:48 2026 (r1931046)
+++ serf/trunk/serf_private.h Thu Jan 1 18:45:34 2026 (r1931047)
@@ -125,6 +125,75 @@ typedef int serf__bool_t; /* Not _Bool *
(result) = integer_; \
} while(0)
+/*** Error callback invocation ***/
+
+/* NOTE: There is no serf__global_error() because the global handler
+ should not be called directly but only as a fallback. */
+
+apr_status_t serf__context_error(const serf_context_t* ctx,
+ apr_status_t status,
+ const char *message);
+apr_status_t serf__connection_error(const serf_connection_t *conn,
+ apr_status_t status,
+ const char *message);
+apr_status_t serf__request_error(const serf_request_t *req,
+ apr_status_t status,
+ const char *message);
+apr_status_t serf__response_error(const serf_request_t *req,
+ apr_status_t status,
+ const char *message);
+apr_status_t serf__incoming_error(const serf_incoming_t *client,
+ apr_status_t status,
+ const char *message);
+apr_status_t serf__incoming_request_error(const serf_incoming_request_t *req,
+ apr_status_t status,
+ const char *message);
+apr_status_t serf__incoming_response_error(const serf_incoming_request_t *req,
+ apr_status_t status,
+ const char *message);
+
+/* The SSL context is a special case sonce it doesn't directly
+ belong to any context or connection. The ssl context implementation
+ calls serf__ssl_context_error() with an serf__ssl_error_ctx_t provided
+ by the caller of the ssl_context function. This is a bit of a pretzel,
+ but the alternative is to only send errors from the SSL context to the
+ global error context, which is less than ideal. */
+
+typedef struct serf__ssl_error_ctx_t serf__ssl_error_ctx_t;
+struct serf__ssl_error_ctx_t
+{
+ apr_status_t (*dispatch)(const void *baton,
+ apr_status_t status,
+ const char *message);
+ void *baton;
+};
+
+/* Error dispatchers for the SSL error context. */
+apr_status_t serf__global_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message);
+apr_status_t serf__context_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message);
+apr_status_t serf__connection_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message);
+apr_status_t serf__request_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message);
+apr_status_t serf__response_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message);
+apr_status_t serf__incoming_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message);
+apr_status_t serf__incoming_request_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message);
+apr_status_t serf__incoming_response_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message);
+
/*** Logging facilities ***/
/* Check for the SERF_DISABLE_LOGGING define, as set by scons. */
@@ -491,6 +560,10 @@ struct serf_context_t {
void *volatile resolve_head;
apr_status_t resolve_init_status;
void *resolve_context;
+
+ /* Error callback */
+ serf_error_cb_t error_callback;
+ void *error_callback_baton;
};
struct serf_listener_t {
@@ -545,6 +618,10 @@ struct serf_incoming_t {
serf_bucket_t *proto_peek_bkt;
serf_incoming_request_t *current_request; /* For HTTP/1 */
+
+ /* Error callback */
+ serf_error_cb_t error_callback;
+ void *error_callback_baton;
};
/* States for the different stages in the lifecycle of a connection. */
@@ -666,6 +743,10 @@ struct serf_connection_t {
/* Configuration shared with buckets and authn plugins */
serf_config_t *config;
+
+ /* Error callback */
+ serf_error_cb_t error_callback;
+ void *error_callback_baton;
};
/* Called by requests that still have outstanding requests to allow cleaning
Modified: serf/trunk/src/context.c
==============================================================================
--- serf/trunk/src/context.c Thu Jan 1 17:33:48 2026 (r1931046)
+++ serf/trunk/src/context.c Thu Jan 1 18:45:34 2026 (r1931047)
@@ -218,6 +218,16 @@ serf_context_t *serf_context_create(apr_
return serf_context_create_ex(NULL, NULL, NULL, pool);
}
+
+void serf_context_error_callback_set(serf_context_t *ctx,
+ serf_error_cb_t callback,
+ void *baton)
+{
+ ctx->error_callback_baton = baton;
+ ctx->error_callback = callback;
+}
+
+
apr_status_t serf_context_prerun(serf_context_t *ctx)
{
apr_status_t status;
Added: serf/trunk/src/error_callbacks.c
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ serf/trunk/src/error_callbacks.c Thu Jan 1 18:45:34 2026
(r1931047)
@@ -0,0 +1,263 @@
+/* ====================================================================
+ * 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 "serf.h"
+#include "serf_private.h"
+
+/* Global error processing. */
+
+static apr_status_t
+default_global_error_callback(void *baton,
+ unsigned source,
+ apr_status_t status,
+ const char *message)
+{
+ return APR_SUCCESS;
+}
+
+static void *global_error_callback_baton = NULL;
+static serf_error_cb_t global_error_callback = default_global_error_callback;
+
+void serf_global_error_callback_set(serf_error_cb_t callback, void *baton)
+{
+ global_error_callback_baton = baton;
+ global_error_callback = callback;
+}
+
+/* The various process_*_error functions set the error source, then either
+ call the appropriate error callback or, if that's not defined, send the
+ message up the hierarchy until it's either handled by a registered
+ callback or thrown away by default_global_error_callback().
+
+ This way, the callers of serf__*_error() don't have to bother with
+ the error source or check if their callbacks have been defined. */
+
+static apr_status_t process_global_error(unsigned source,
+ apr_status_t status,
+ const char *message)
+{
+ if (0 == (source & SERF_ERROR_CB_MASK)) {
+ source |= SERF_ERROR_CB_GLOBAL;
+ }
+ return global_error_callback(global_error_callback_baton,
+ source, status, message);
+}
+
+/* Context error processing. */
+
+static apr_status_t process_context_error(unsigned source,
+ const serf_context_t* ctx,
+ apr_status_t status,
+ const char *message)
+{
+ if (0 == (source & SERF_ERROR_CB_MASK)) {
+ source |= SERF_ERROR_CB_CONTEXT;
+ }
+ if (ctx->error_callback) {
+ return ctx->error_callback(ctx->error_callback_baton,
+ source, status, message);
+ }
+ return process_global_error(source, status, message);
+}
+
+apr_status_t serf__context_error(const serf_context_t* ctx,
+ apr_status_t status,
+ const char *message)
+{
+ return process_context_error(SERF_ERROR_CB_CONTEXT,
+ ctx, status, message);
+}
+
+/* Connection error processing. */
+
+static apr_status_t process_connection_error(unsigned source,
+ const serf_connection_t *conn,
+ apr_status_t status,
+ const char *message)
+{
+ if (0 == (source & SERF_ERROR_CB_MASK)) {
+ source |= SERF_ERROR_CB_OUTGOING;
+ }
+ if (conn->error_callback) {
+ return conn->error_callback(conn->error_callback_baton,
+ source, status, message);
+ }
+ return process_context_error(source, conn->ctx, status, message);
+}
+
+apr_status_t serf__connection_error(const serf_connection_t *conn,
+ apr_status_t status,
+ const char *message)
+{
+ return process_connection_error(SERF_ERROR_CB_OUTGOING,
+ conn, status, message);
+}
+
+apr_status_t serf__request_error(const serf_request_t *req,
+ apr_status_t status,
+ const char *message)
+{
+ return process_connection_error(SERF_ERROR_CB_OUTGOING
+ | SERF_ERROR_CB_REQUEST,
+ req->conn, status, message);
+}
+
+apr_status_t serf__response_error(const serf_request_t *req,
+ apr_status_t status,
+ const char *message)
+{
+ return process_connection_error(SERF_ERROR_CB_OUTGOING
+ | SERF_ERROR_CB_RESPONSE,
+ req->conn, status, message);
+}
+
+/* Incoming error processing. */
+
+static apr_status_t process_incoming_error(unsigned source,
+ const serf_incoming_t *client,
+ apr_status_t status,
+ const char *message)
+{
+ if (0 == (source & SERF_ERROR_CB_MASK)) {
+ source |= SERF_ERROR_CB_INCOMING;
+ }
+ if (client->error_callback) {
+ return client->error_callback(client->error_callback_baton,
+ source, status, message);
+ }
+ return process_context_error(source, client->ctx, status, message);
+}
+
+apr_status_t serf__incoming_error(const serf_incoming_t *client,
+ apr_status_t status,
+ const char *message)
+{
+ return process_incoming_error(SERF_ERROR_CB_INCOMING,
+ client, status, message);
+}
+
+apr_status_t serf__incoming_request_error(const serf_incoming_request_t *req,
+ apr_status_t status,
+ const char *message)
+{
+ return process_incoming_error(SERF_ERROR_CB_INCOMING
+ | SERF_ERROR_CB_REQUEST,
+ req->incoming, status, message);
+}
+
+apr_status_t serf__incoming_response_error(const serf_incoming_request_t *req,
+ apr_status_t status,
+ const char *message)
+{
+ return process_incoming_error(SERF_ERROR_CB_INCOMING
+ | SERF_ERROR_CB_RESPONSE,
+ req->incoming, status, message);
+}
+
+/* Errors from the SSL context.
+
+ Callers of the SSL functions can create an serf__ssl_error_ctx_t
+ on the stack and pass it to the called function, which can then
+ dispatch any errors it generates to the appropriate callback. */
+
+apr_status_t serf__global_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message)
+{
+ /* Ignores the baton, since there's only one global error callback. */
+ return process_global_error(SERF_ERROR_CB_SSL_CONTEXT
+ | SERF_ERROR_CB_GLOBAL,
+ status, message);
+}
+
+apr_status_t serf__context_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message)
+{
+ const serf_context_t *const ctx = baton;
+ return process_context_error(SERF_ERROR_CB_SSL_CONTEXT
+ | SERF_ERROR_CB_CONTEXT,
+ ctx, status, message);
+}
+
+
+apr_status_t serf__connection_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message)
+{
+ const serf_connection_t *const conn = baton;
+ return process_connection_error(SERF_ERROR_CB_SSL_CONTEXT
+ | SERF_ERROR_CB_OUTGOING,
+ conn, status, message);
+}
+
+apr_status_t serf__request_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message)
+{
+ const serf_request_t *const req = baton;
+ return process_connection_error(SERF_ERROR_CB_SSL_CONTEXT
+ | SERF_ERROR_CB_OUTGOING
+ | SERF_ERROR_CB_REQUEST,
+ req->conn, status, message);
+}
+
+apr_status_t serf__response_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message)
+{
+ const serf_request_t *const req = baton;
+ return process_connection_error(SERF_ERROR_CB_SSL_CONTEXT
+ | SERF_ERROR_CB_OUTGOING
+ | SERF_ERROR_CB_RESPONSE,
+ req->conn, status, message);
+}
+
+apr_status_t serf__incoming_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message)
+{
+ const serf_incoming_t *const client = baton;
+ return process_incoming_error(SERF_ERROR_CB_SSL_CONTEXT
+ | SERF_ERROR_CB_INCOMING,
+ client, status, message);
+}
+
+apr_status_t serf__incoming_request_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message)
+{
+ const serf_incoming_request_t *const req = baton;
+ return process_incoming_error(SERF_ERROR_CB_SSL_CONTEXT
+ | SERF_ERROR_CB_INCOMING
+ | SERF_ERROR_CB_REQUEST,
+ req->incoming, status, message);
+}
+
+apr_status_t serf__incoming_response_ssl_error(const void *baton,
+ apr_status_t status,
+ const char *message)
+{
+ const serf_incoming_request_t *const req = baton;
+ return process_incoming_error(SERF_ERROR_CB_SSL_CONTEXT
+ | SERF_ERROR_CB_INCOMING
+ | SERF_ERROR_CB_RESPONSE,
+ req->incoming, status, message);
+}
Modified: serf/trunk/src/incoming.c
==============================================================================
--- serf/trunk/src/incoming.c Thu Jan 1 17:33:48 2026 (r1931046)
+++ serf/trunk/src/incoming.c Thu Jan 1 18:45:34 2026 (r1931047)
@@ -707,6 +707,14 @@ apr_status_t serf_listener_create(
return APR_SUCCESS;
}
+void serf_incoming_error_callback_set(serf_incoming_t *client,
+ serf_error_cb_t callback,
+ void *baton)
+{
+ client->error_callback_baton = baton;
+ client->error_callback = callback;
+}
+
apr_status_t serf__incoming_update_pollset(serf_incoming_t *client)
{
serf_context_t *ctx = client->ctx;
Modified: serf/trunk/src/outgoing.c
==============================================================================
--- serf/trunk/src/outgoing.c Thu Jan 1 17:33:48 2026 (r1931046)
+++ serf/trunk/src/outgoing.c Thu Jan 1 18:45:34 2026 (r1931047)
@@ -1452,6 +1452,15 @@ apr_status_t serf_connection_create_asyn
}
+void serf_connection_error_callback_set(serf_connection_t *conn,
+ serf_error_cb_t callback,
+ void *baton)
+{
+ conn->error_callback_baton = baton;
+ conn->error_callback = callback;
+}
+
+
apr_status_t serf_connection_reset(
serf_connection_t *conn)
{
Modified: serf/trunk/test/test_util.c
==============================================================================
--- serf/trunk/test/test_util.c Thu Jan 1 17:33:48 2026 (r1931046)
+++ serf/trunk/test/test_util.c Thu Jan 1 18:45:34 2026 (r1931047)
@@ -27,6 +27,15 @@
#include <stdlib.h>
+#ifdef WIN32
+#include <io.h>
+#define isatty _isatty
+#elif HAVE_UNISTD_H
+#include <unistd.h>
+#else
+#define isatty(x) 0
+#endif
+
#include "serf.h"
#include "test_serf.h"
@@ -464,6 +473,28 @@ apr_status_t dummy_authn_callback(char *
/* Test utility functions, to be used with the MockHTTPinC framework */
/*****************************************************************************/
+static apr_status_t test_error_callback(void *baton,
+ unsigned source,
+ apr_status_t status,
+ const char *message)
+{
+ /* We can has nice colours? Use ANSI escape codes on terminals. */
+ const char *const ERROR = (isatty(fileno(stderr))
+ ? "\033[1;31m" "ERROR" "\033[0;m"
+ : "ERROR");
+
+ fprintf(stderr, "%s: <%d> %c%c%c%c%c %s\n", ERROR, status,
+ ((source & SERF_ERROR_CB_SSL_CONTEXT) ? '*' : '-'),
+ ((source & SERF_ERROR_CB_GLOBAL) ? 'g' : '-'),
+ ((source & SERF_ERROR_CB_CONTEXT) ? 'c' : '-'),
+ ((source & SERF_ERROR_CB_OUTGOING) ? 'o'
+ : ((source & SERF_ERROR_CB_INCOMING) ? 'i' : '-')),
+ ((source & SERF_ERROR_CB_REQUEST) ? 'q'
+ : ((source & SERF_ERROR_CB_RESPONSE) ? 'p' : '-')),
+ message);
+ return APR_SUCCESS;
+}
+
apr_status_t
setup_test_context(test_baton_t *tb, apr_pool_t *pool)
{
@@ -474,6 +505,7 @@ setup_test_context(test_baton_t *tb, apr
tb->context = serf_context_create(pool);
if (TEST_VERBOSE) {
+ serf_global_error_callback_set(test_error_callback, NULL);
status = serf_logging_create_stream_output(&output, tb->context,
SERF_LOG_DEBUG,
SERF_LOGCOMP_ALL,