From 4758f612674113f95cb86be68c9b7ed0ebc092b8 Mon Sep 17 00:00:00 2001 From: Adam Dickmeiss Date: Mon, 21 Sep 2026 19:42:07 +0200 Subject: [PATCH] Support cs_set_head_only for unix comstack --- src/comstack-p.h | 4 +++ src/comstack.c | 12 +++++++ src/tcpip.c | 2 +- src/unix.c | 13 +++++++ src/yaz/comstack.h | 6 ++++ test/test_comstack.c | 82 +++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 117 insertions(+), 2 deletions(-) diff --git a/src/comstack-p.h b/src/comstack-p.h index b2740c45..e7611cd7 100644 --- a/src/comstack-p.h +++ b/src/comstack-p.h @@ -38,6 +38,10 @@ YAZ_BEGIN_CDECL const char *yaz_tcpip_get_error_details(COMSTACK cs); +int yaz_tcpip_set_head_only(COMSTACK cs, int head_only); +#ifndef WIN32 +int yaz_unix_set_head_only(COMSTACK cs, int head_only); +#endif /* * Parses an HTTP chunked body. diff --git a/src/comstack.c b/src/comstack.c index 849897fd..2c85ed8a 100644 --- a/src/comstack.c +++ b/src/comstack.c @@ -521,6 +521,18 @@ int cs_complete_auto_head(const char *buf, int len) return cs_complete_auto_x(buf, len, 1); } +int cs_set_head_only(COMSTACK cs, int head_only) +{ + if (cs->type == tcpip_type || cs->type == ssl_type) + return yaz_tcpip_set_head_only(cs, head_only); +#ifndef WIN32 + if (cs->type == unix_type) + return yaz_unix_set_head_only(cs, head_only); +#endif + cs->cerrno = CSOUTSTATE; + return -1; +} + void cs_set_max_recv_bytes(COMSTACK cs, int max_recv_bytes) { cs->max_recv_bytes = max_recv_bytes; diff --git a/src/tcpip.c b/src/tcpip.c index 8c10a05e..6ad50563 100644 --- a/src/tcpip.c +++ b/src/tcpip.c @@ -1769,7 +1769,7 @@ int cs_get_peer_certificate_x509(COMSTACK cs, char **buf, int *len) return 0; } -int cs_set_head_only(COMSTACK cs, int head_only) +int yaz_tcpip_set_head_only(COMSTACK cs, int head_only) { int (*completer)(const char *buf, int len) = head_only ? cs_complete_auto_head : cs_complete_auto; diff --git a/src/unix.c b/src/unix.c index 9dd58f51..9c8ac14c 100644 --- a/src/unix.c +++ b/src/unix.c @@ -47,6 +47,7 @@ #include #include #include +#include "comstack-p.h" #ifndef YAZ_SOCKLEN_T #define YAZ_SOCKLEN_T int @@ -309,6 +310,18 @@ struct sockaddr_un *unix_strtoaddr(const char *str) return &add; } +int yaz_unix_set_head_only(COMSTACK cs, int head_only) +{ + if (cs->type == unix_type) + { + unix_state *sp = (unix_state *)cs->cprivate; + sp->complete = head_only ? cs_complete_auto_head : cs_complete_auto; + return 0; + } + cs->cerrno = CSOUTSTATE; + return -1; +} + static int unix_more(COMSTACK h) { unix_state *sp = (unix_state *)h->cprivate; diff --git a/src/yaz/comstack.h b/src/yaz/comstack.h index 23d59354..a4e9149f 100644 --- a/src/yaz/comstack.h +++ b/src/yaz/comstack.h @@ -266,6 +266,12 @@ YAZ_EXPORT void cs_print_session_info(COMSTACK cs); YAZ_EXPORT int cs_parse_host(const char *uri, const char **host, CS_TYPE *t, enum oid_proto *proto, char **connect_host); +/** \brief Selects HTTP header-only framing for TCP/IP, SSL or UNIX transports + \param cs COMSTACK handle + \param head_only nonzero to stop at the end of HTTP headers; zero to + restore normal message framing + \returns 0 on success, -1 with CSOUTSTATE for unsupported transports + */ YAZ_EXPORT int cs_set_head_only(COMSTACK cs, int head_only); /* diff --git a/test/test_comstack.c b/test/test_comstack.c index 2da499e8..f58e1212 100644 --- a/test/test_comstack.c +++ b/test/test_comstack.c @@ -14,6 +14,14 @@ #include #include #include +#include +#include + +#ifndef WIN32 +#include +#include +#include +#endif static void tst_http_request(void) { @@ -489,9 +497,9 @@ static void tst_cs_get_error_tcp(void) cs_close(cs); } +#if HAVE_GNUTLS_H static void tst_cs_get_error_ssl(void) { -#if HAVE_GNUTLS_H const char *details = NULL; COMSTACK ssl_cs = cs_create(ssl_type, CS_FLAGS_BLOCKING, PROTO_Z3950); @@ -507,6 +515,75 @@ static void tst_cs_get_error_ssl(void) YAZ_CHECK_EQ(cs_get_error(ssl_cs, &details), CSERRORSSL); YAZ_CHECK(details && *details); cs_close(ssl_cs); +} +#endif + +#ifndef WIN32 +static void tst_head_only_transport(CS_TYPE type) +{ + static const char head[] = + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\n"; + static const char response[] = + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata"; + int fd[2], r, saved_errno; + COMSTACK cs; + char *buf = 0; + int bufsize = 0; + + r = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); + saved_errno = errno; + if (r == -1 && (saved_errno == EPERM || saved_errno == EACCES)) + { + fprintf(stderr, "Skipping head-only framing: socketpair denied\n"); + return; + } + YAZ_CHECK_EQ(r, 0); + if (r != 0) + return; + cs = cs_createbysocket(fd[0], type, CS_FLAGS_BLOCKING, PROTO_HTTP); + YAZ_CHECK(cs); + if (!cs) + { + close(fd[0]); + close(fd[1]); + return; + } + + /* A HEAD response has no body despite Content-Length. Follow it with + a normal response to verify switching back, including buffered data. + The SSL transport has no TLS session here; only framing is tested. */ + YAZ_CHECK_EQ(write(fd[1], head, sizeof(head) - 1), sizeof(head) - 1); + YAZ_CHECK_EQ(write(fd[1], response, sizeof(response) - 1), + sizeof(response) - 1); + close(fd[1]); + YAZ_CHECK_EQ(cs_set_head_only(cs, 1), 0); + r = cs_get(cs, &buf, &bufsize); + YAZ_CHECK_EQ(r, sizeof(head) - 1); + if (r == sizeof(head) - 1) + YAZ_CHECK(!memcmp(buf, head, r)); + YAZ_CHECK_EQ(cs_set_head_only(cs, 0), 0); + r = cs_get(cs, &buf, &bufsize); + YAZ_CHECK_EQ(r, sizeof(response) - 1); + if (r == sizeof(response) - 1) + YAZ_CHECK(!memcmp(buf, response, r)); + cs_close(cs); + xfree(buf); +} +#endif + +static void tst_cs_set_head_only(void) +{ + struct comstack unsupported; + + memset(&unsupported, 0, sizeof(unsupported)); + YAZ_CHECK_EQ(cs_set_head_only(&unsupported, 1), -1); + YAZ_CHECK_EQ(cs_errno(&unsupported), CSOUTSTATE); +#ifndef WIN32 + tst_head_only_transport(tcpip_type); + tst_head_only_transport(unix_type); +#if HAVE_GNUTLS_H + tst_head_only_transport(ssl_type); +#endif #endif } @@ -520,7 +597,10 @@ int main (int argc, char **argv) tst_http_response(); tst_cs_get_host_args(); tst_cs_get_error_tcp(); +#if HAVE_GNUTLS_H tst_cs_get_error_ssl(); +#endif + tst_cs_set_head_only(); YAZ_CHECK_TERM; }