aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIvan Stakhov <50211739+left-try@users.noreply.github.com>2024-10-19 18:23:18 +0300
committerGitHub <noreply@github.com>2024-10-19 18:23:18 +0300
commit7ca76b8768adcdd1205b7bc8c7000be3bbc281fe (patch)
treec273e6772e14b585306930976852508356805d0f
parent3d02f65dcb90ae02ab840df8495f71655bd4cbbc (diff)
parentc9b804517d25168ed4c03c70fcc808d38b86e785 (diff)
downloadrspamd-7ca76b8768adcdd1205b7bc8c7000be3bbc281fe.tar.gz
rspamd-7ca76b8768adcdd1205b7bc8c7000be3bbc281fe.zip
Merge branch 'rspamd:master' into master
-rw-r--r--CMakeLists.txt12
-rw-r--r--conf/modules.d/url_redirector.conf1
-rw-r--r--config.h.in1
-rw-r--r--contrib/DEPENDENCY_INFO.md2
-rw-r--r--contrib/hiredis/CMakeLists.txt7
-rw-r--r--contrib/hiredis/adapters/libev.h224
-rw-r--r--contrib/hiredis/alloc.c90
-rw-r--r--contrib/hiredis/alloc.h96
-rw-r--r--contrib/hiredis/async.c665
-rw-r--r--contrib/hiredis/async.h31
-rw-r--r--contrib/hiredis/async_private.h75
-rw-r--r--contrib/hiredis/dict.c47
-rw-r--r--contrib/hiredis/dict.h3
-rw-r--r--contrib/hiredis/fmacros.h17
-rw-r--r--contrib/hiredis/hiredis.c602
-rw-r--r--contrib/hiredis/hiredis.h228
-rw-r--r--contrib/hiredis/hiredis_ssl.h163
-rw-r--r--contrib/hiredis/net.c408
-rw-r--r--contrib/hiredis/net.h10
-rw-r--r--contrib/hiredis/read.c481
-rw-r--r--contrib/hiredis/read.h37
-rw-r--r--contrib/hiredis/sds.c583
-rw-r--r--contrib/hiredis/sds.h212
-rw-r--r--contrib/hiredis/sdsalloc.h44
-rw-r--r--contrib/hiredis/sockcompat.c280
-rw-r--r--contrib/hiredis/sockcompat.h95
-rw-r--r--contrib/hiredis/ssl.c617
-rw-r--r--contrib/hiredis/test.c2435
-rw-r--r--contrib/hiredis/win32.h56
-rw-r--r--contrib/publicsuffix/effective_tld_names.dat539
-rwxr-xr-xdebian/rules2
-rw-r--r--rpm/rspamd.spec3
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/client/CMakeLists.txt4
-rw-r--r--src/client/rspamdclient.c1
-rw-r--r--src/libcryptobox/cryptobox.c19
-rw-r--r--src/libcryptobox/cryptobox.h3
-rw-r--r--src/libserver/dkim.c53
-rw-r--r--src/libserver/protocol.c481
-rw-r--r--src/libserver/protocol_internal.h2
-rw-r--r--src/libserver/ssl_util.c19
-rw-r--r--src/libserver/task.c6
-rw-r--r--src/libserver/worker_util.c3
-rw-r--r--src/libstat/stat_internal.h10
-rw-r--r--src/libstat/stat_process.c3
-rw-r--r--src/lua/lua_http.c3
-rw-r--r--src/ragel/smtp_base.rl1
-rw-r--r--src/rspamadm/CMakeLists.txt4
-rw-r--r--src/rspamd.c2
-rw-r--r--src/rspamd_proxy.c3
50 files changed, 7127 insertions, 1560 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3d384fa1b..6daced05a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -798,13 +798,11 @@ ADD_CUSTOM_TARGET(dist ${CMAKE_SOURCE_DIR}/dist.sh
COMMENT "Create source distribution"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-IF (NOT DEBIAN_BUILD)
- ADD_CUSTOM_TARGET(check DEPENDS rspamd-test-cxx rspamd-test)
- ADD_CUSTOM_TARGET(run-test DEPENDS check
- COMMAND test/rspamd-test-cxx
- COMMAND sh -c 'LUA_PATH="${CMAKE_SOURCE_DIR}/lualib/?.lua\;${CMAKE_SOURCE_DIR}/lualib/?/?.lua\;${CMAKE_SOURCE_DIR}/lualib/?/init.lua\;${CMAKE_SOURCE_DIR}/contrib/lua-?/?.lua"
- test/rspamd-test -p /rspamd/lua')
-ENDIF (NOT DEBIAN_BUILD)
+ADD_CUSTOM_TARGET(check DEPENDS rspamd-test-cxx rspamd-test)
+ADD_CUSTOM_TARGET(run-test DEPENDS check
+ COMMAND test/rspamd-test-cxx
+ COMMAND sh -c 'LUA_PATH="${CMAKE_SOURCE_DIR}/lualib/?.lua\;${CMAKE_SOURCE_DIR}/lualib/?/?.lua\;${CMAKE_SOURCE_DIR}/lualib/?/init.lua\;${CMAKE_SOURCE_DIR}/contrib/lua-?/?.lua"
+ test/rspamd-test -p /rspamd/lua')
# PVS Studio
diff --git a/conf/modules.d/url_redirector.conf b/conf/modules.d/url_redirector.conf
index da3b5bb13..ec21f915f 100644
--- a/conf/modules.d/url_redirector.conf
+++ b/conf/modules.d/url_redirector.conf
@@ -16,7 +16,6 @@ url_redirector {
expire = 1d; # 1 day by default
timeout = 10; # 10 seconds by default
nested_limit = 1; # How many redirects to follow
- #proxy = "http://example.com:3128"; # Send request through proxy
key_prefix = "rdr:"; # default hash name
check_ssl = false; # check ssl certificates
max_size = 10k; # maximum body to process
diff --git a/config.h.in b/config.h.in
index 9aff90783..0ed2cd6b2 100644
--- a/config.h.in
+++ b/config.h.in
@@ -116,6 +116,7 @@
#cmakedefine WITH_FASTTEXT 1
#cmakedefine BACKWARD_ENABLE 1
#cmakedefine HAVE_BUILTIN_CPU_SUPPORTS 1
+#cmakedefine RSPAMD_LEGACY_SSL_PROVIDER 1
#cmakedefine DISABLE_PTHREAD_MUTEX 1
diff --git a/contrib/DEPENDENCY_INFO.md b/contrib/DEPENDENCY_INFO.md
index ccdde60bb..86598129c 100644
--- a/contrib/DEPENDENCY_INFO.md
+++ b/contrib/DEPENDENCY_INFO.md
@@ -4,7 +4,7 @@
|------------------------|---------|---------------------|---------|---------------------|
| aho-corasick | ? | LGPL-3.0 | YES | lowercase support |
| cdb | 1.1.0 | Public Domain / CC0 | NO | |
-| hiredis | 0.13.3 | BSD-3-Clause | YES | many changes |
+| hiredis | 1.2.0 | BSD-3-Clause | NO | |
| libev | 4.33 | BSD-2-Clause | YES | many changes |
| lc-btrie | ? | BSD-3-Clause | YES | mempool support |
| libottery | ? | Public Domain / CC0 | YES | many changes |
diff --git a/contrib/hiredis/CMakeLists.txt b/contrib/hiredis/CMakeLists.txt
index 1e056319f..dfd1cd261 100644
--- a/contrib/hiredis/CMakeLists.txt
+++ b/contrib/hiredis/CMakeLists.txt
@@ -1,9 +1,12 @@
-SET(HIREDISSRC async.c
+SET(HIREDISSRC alloc.c
+ async.c
dict.c
hiredis.c
net.c
read.c
- sds.c)
+ sds.c
+ sockcompat.c
+ ssl.c)
SET(HIREDIS_CFLAGS "")
ADD_LIBRARY(rspamd-hiredis STATIC ${HIREDISSRC})
diff --git a/contrib/hiredis/adapters/libev.h b/contrib/hiredis/adapters/libev.h
index 9cf00dfd9..7e0cffaf6 100644
--- a/contrib/hiredis/adapters/libev.h
+++ b/contrib/hiredis/adapters/libev.h
@@ -1,4 +1,20 @@
/*
+ * Copyright 2024 Vsevolod Stakhov
+ *
+ * Licensed 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.
+ */
+
+/*
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
@@ -37,111 +53,163 @@
#include "../async.h"
typedef struct redisLibevEvents {
- redisAsyncContext *context;
- struct ev_loop *loop;
- int reading, writing;
- ev_io rev, wev;
+ redisAsyncContext *context;
+ struct ev_loop *loop;
+ int reading, writing;
+ ev_io rev, wev;
+ ev_timer timer;
} redisLibevEvents;
-static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) {
+static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents)
+{
#if EV_MULTIPLICITY
- ((void)loop);
+ ((void) EV_A);
#endif
- ((void)revents);
+ ((void) revents);
- redisLibevEvents *e = (redisLibevEvents*)watcher->data;
- redisAsyncHandleRead(e->context);
+ redisLibevEvents *e = (redisLibevEvents *) watcher->data;
+ redisAsyncHandleRead(e->context);
}
-static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) {
+static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents)
+{
#if EV_MULTIPLICITY
- ((void)loop);
+ ((void) EV_A);
#endif
- ((void)revents);
+ ((void) revents);
- redisLibevEvents *e = (redisLibevEvents*)watcher->data;
- redisAsyncHandleWrite(e->context);
+ redisLibevEvents *e = (redisLibevEvents *) watcher->data;
+ redisAsyncHandleWrite(e->context);
}
-static void redisLibevAddRead(void *privdata) {
- redisLibevEvents *e = (redisLibevEvents*)privdata;
- struct ev_loop *loop = e->loop;
- ((void)loop);
- if (!e->reading) {
- e->reading = 1;
- ev_io_start(EV_A_ &e->rev);
- }
+static void redisLibevAddRead(void *privdata)
+{
+ redisLibevEvents *e = (redisLibevEvents *) privdata;
+#if EV_MULTIPLICITY
+ struct ev_loop *loop = e->loop;
+#endif
+ if (!e->reading) {
+ e->reading = 1;
+ ev_io_start(EV_A_ & e->rev);
+ }
}
-static void redisLibevDelRead(void *privdata) {
- redisLibevEvents *e = (redisLibevEvents*)privdata;
- struct ev_loop *loop = e->loop;
- ((void)loop);
- if (e->reading) {
- e->reading = 0;
- ev_io_stop(EV_A_ &e->rev);
- }
+static void redisLibevDelRead(void *privdata)
+{
+ redisLibevEvents *e = (redisLibevEvents *) privdata;
+#if EV_MULTIPLICITY
+ struct ev_loop *loop = e->loop;
+#endif
+ if (e->reading) {
+ e->reading = 0;
+ ev_io_stop(EV_A_ & e->rev);
+ }
}
-static void redisLibevAddWrite(void *privdata) {
- redisLibevEvents *e = (redisLibevEvents*)privdata;
- struct ev_loop *loop = e->loop;
- ((void)loop);
- if (!e->writing) {
- e->writing = 1;
- ev_io_start(EV_A_ &e->wev);
- }
+static void redisLibevAddWrite(void *privdata)
+{
+ redisLibevEvents *e = (redisLibevEvents *) privdata;
+#if EV_MULTIPLICITY
+ struct ev_loop *loop = e->loop;
+#endif
+ if (!e->writing) {
+ e->writing = 1;
+ ev_io_start(EV_A_ & e->wev);
+ }
}
-static void redisLibevDelWrite(void *privdata) {
- redisLibevEvents *e = (redisLibevEvents*)privdata;
- struct ev_loop *loop = e->loop;
- ((void)loop);
- if (e->writing) {
- e->writing = 0;
- ev_io_stop(EV_A_ &e->wev);
- }
+static void redisLibevDelWrite(void *privdata)
+{
+ redisLibevEvents *e = (redisLibevEvents *) privdata;
+#if EV_MULTIPLICITY
+ struct ev_loop *loop = e->loop;
+#endif
+ if (e->writing) {
+ e->writing = 0;
+ ev_io_stop(EV_A_ & e->wev);
+ }
}
-static void redisLibevCleanup(void *privdata) {
- redisLibevEvents *e = (redisLibevEvents*)privdata;
- redisLibevDelRead(privdata);
- redisLibevDelWrite(privdata);
- free(e);
+static void redisLibevStopTimer(void *privdata)
+{
+ redisLibevEvents *e = (redisLibevEvents *) privdata;
+#if EV_MULTIPLICITY
+ struct ev_loop *loop = e->loop;
+#endif
+ ev_timer_stop(EV_A_ & e->timer);
+}
+
+static void redisLibevCleanup(void *privdata)
+{
+ redisLibevEvents *e = (redisLibevEvents *) privdata;
+ redisLibevDelRead(privdata);
+ redisLibevDelWrite(privdata);
+ redisLibevStopTimer(privdata);
+ hi_free(e);
+}
+
+static void redisLibevTimeout(EV_P_ ev_timer *timer, int revents)
+{
+#if EV_MULTIPLICITY
+ ((void) EV_A);
+#endif
+ ((void) revents);
+ redisLibevEvents *e = (redisLibevEvents *) timer->data;
+ redisAsyncHandleTimeout(e->context);
}
-static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
- redisContext *c = &(ac->c);
- redisLibevEvents *e;
+static void redisLibevSetTimeout(void *privdata, struct timeval tv)
+{
+ redisLibevEvents *e = (redisLibevEvents *) privdata;
+#if EV_MULTIPLICITY
+ struct ev_loop *loop = e->loop;
+#endif
+
+ if (!ev_is_active(&e->timer)) {
+ ev_init(&e->timer, redisLibevTimeout);
+ e->timer.data = e;
+ }
+
+ e->timer.repeat = tv.tv_sec + tv.tv_usec / 1000000.00;
+ ev_timer_again(EV_A_ & e->timer);
+}
+
+static int redisLibevAttach(EV_P_ redisAsyncContext *ac)
+{
+ redisContext *c = &(ac->c);
+ redisLibevEvents *e;
+
+ /* Nothing should be attached when something is already attached */
+ if (ac->ev.data != NULL)
+ return REDIS_ERR;
- /* Nothing should be attached when something is already attached */
- if (ac->ev.data != NULL)
- return REDIS_ERR;
+ /* Create container for context and r/w events */
+ e = (redisLibevEvents *) hi_calloc(1, sizeof(*e));
+ if (e == NULL)
+ return REDIS_ERR;
- /* Create container for context and r/w events */
- e = (redisLibevEvents*)malloc(sizeof(*e));
- e->context = ac;
+ e->context = ac;
#if EV_MULTIPLICITY
- e->loop = loop;
+ e->loop = EV_A;
#else
- e->loop = NULL;
+ e->loop = NULL;
#endif
- e->reading = e->writing = 0;
- e->rev.data = e;
- e->wev.data = e;
-
- /* Register functions to start/stop listening for events */
- ac->ev.addRead = redisLibevAddRead;
- ac->ev.delRead = redisLibevDelRead;
- ac->ev.addWrite = redisLibevAddWrite;
- ac->ev.delWrite = redisLibevDelWrite;
- ac->ev.cleanup = redisLibevCleanup;
- ac->ev.data = e;
-
- /* Initialize read/write events */
- ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ);
- ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE);
- return REDIS_OK;
+ e->rev.data = e;
+ e->wev.data = e;
+
+ /* Register functions to start/stop listening for events */
+ ac->ev.addRead = redisLibevAddRead;
+ ac->ev.delRead = redisLibevDelRead;
+ ac->ev.addWrite = redisLibevAddWrite;
+ ac->ev.delWrite = redisLibevDelWrite;
+ ac->ev.cleanup = redisLibevCleanup;
+ ac->ev.scheduleTimer = redisLibevSetTimeout;
+ ac->ev.data = e;
+
+ /* Initialize read/write events */
+ ev_io_init(&e->rev, redisLibevReadEvent, c->fd, EV_READ);
+ ev_io_init(&e->wev, redisLibevWriteEvent, c->fd, EV_WRITE);
+ return REDIS_OK;
}
#endif
diff --git a/contrib/hiredis/alloc.c b/contrib/hiredis/alloc.c
new file mode 100644
index 000000000..0902286c7
--- /dev/null
+++ b/contrib/hiredis/alloc.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "fmacros.h"
+#include "alloc.h"
+#include <string.h>
+#include <stdlib.h>
+
+hiredisAllocFuncs hiredisAllocFns = {
+ .mallocFn = malloc,
+ .callocFn = calloc,
+ .reallocFn = realloc,
+ .strdupFn = strdup,
+ .freeFn = free,
+};
+
+/* Override hiredis' allocators with ones supplied by the user */
+hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *override) {
+ hiredisAllocFuncs orig = hiredisAllocFns;
+
+ hiredisAllocFns = *override;
+
+ return orig;
+}
+
+/* Reset allocators to use libc defaults */
+void hiredisResetAllocators(void) {
+ hiredisAllocFns = (hiredisAllocFuncs) {
+ .mallocFn = malloc,
+ .callocFn = calloc,
+ .reallocFn = realloc,
+ .strdupFn = strdup,
+ .freeFn = free,
+ };
+}
+
+#ifdef _WIN32
+
+void *hi_malloc(size_t size) {
+ return hiredisAllocFns.mallocFn(size);
+}
+
+void *hi_calloc(size_t nmemb, size_t size) {
+ /* Overflow check as the user can specify any arbitrary allocator */
+ if (SIZE_MAX / size < nmemb)
+ return NULL;
+
+ return hiredisAllocFns.callocFn(nmemb, size);
+}
+
+void *hi_realloc(void *ptr, size_t size) {
+ return hiredisAllocFns.reallocFn(ptr, size);
+}
+
+char *hi_strdup(const char *str) {
+ return hiredisAllocFns.strdupFn(str);
+}
+
+void hi_free(void *ptr) {
+ hiredisAllocFns.freeFn(ptr);
+}
+
+#endif
diff --git a/contrib/hiredis/alloc.h b/contrib/hiredis/alloc.h
new file mode 100644
index 000000000..771f9fee5
--- /dev/null
+++ b/contrib/hiredis/alloc.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HIREDIS_ALLOC_H
+#define HIREDIS_ALLOC_H
+
+#include <stddef.h> /* for size_t */
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Structure pointing to our actually configured allocators */
+typedef struct hiredisAllocFuncs {
+ void *(*mallocFn)(size_t);
+ void *(*callocFn)(size_t,size_t);
+ void *(*reallocFn)(void*,size_t);
+ char *(*strdupFn)(const char*);
+ void (*freeFn)(void*);
+} hiredisAllocFuncs;
+
+hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *ha);
+void hiredisResetAllocators(void);
+
+#ifndef _WIN32
+
+/* Hiredis' configured allocator function pointer struct */
+extern hiredisAllocFuncs hiredisAllocFns;
+
+static inline void *hi_malloc(size_t size) {
+ return hiredisAllocFns.mallocFn(size);
+}
+
+static inline void *hi_calloc(size_t nmemb, size_t size) {
+ /* Overflow check as the user can specify any arbitrary allocator */
+ if (SIZE_MAX / size < nmemb)
+ return NULL;
+
+ return hiredisAllocFns.callocFn(nmemb, size);
+}
+
+static inline void *hi_realloc(void *ptr, size_t size) {
+ return hiredisAllocFns.reallocFn(ptr, size);
+}
+
+static inline char *hi_strdup(const char *str) {
+ return hiredisAllocFns.strdupFn(str);
+}
+
+static inline void hi_free(void *ptr) {
+ hiredisAllocFns.freeFn(ptr);
+}
+
+#else
+
+void *hi_malloc(size_t size);
+void *hi_calloc(size_t nmemb, size_t size);
+void *hi_realloc(void *ptr, size_t size);
+char *hi_strdup(const char *str);
+void hi_free(void *ptr);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* HIREDIS_ALLOC_H */
diff --git a/contrib/hiredis/async.c b/contrib/hiredis/async.c
index afa541304..f82f567f8 100644
--- a/contrib/hiredis/async.c
+++ b/contrib/hiredis/async.c
@@ -30,9 +30,12 @@
*/
#include "fmacros.h"
+#include "alloc.h"
#include <stdlib.h>
#include <string.h>
+#ifndef _MSC_VER
#include <strings.h>
+#endif
#include <assert.h>
#include <ctype.h>
#include <errno.h>
@@ -40,25 +43,18 @@
#include "net.h"
#include "dict.c"
#include "sds.h"
+#include "win32.h"
-#define _EL_ADD_READ(ctx) do { \
- if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
- } while(0)
-#define _EL_DEL_READ(ctx) do { \
- if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
- } while(0)
-#define _EL_ADD_WRITE(ctx) do { \
- if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
- } while(0)
-#define _EL_DEL_WRITE(ctx) do { \
- if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
- } while(0)
-#define _EL_CLEANUP(ctx) do { \
- if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
- } while(0);
-
-/* Forward declaration of function in hiredis.c */
+#include "async_private.h"
+
+#ifdef NDEBUG
+#undef assert
+#define assert(e) (void)(e)
+#endif
+
+/* Forward declarations of hiredis.c functions */
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
+void __redisSetError(redisContext *c, int type, const char *str);
/* Functions managing dictionary of callbacks for pub/sub. */
static unsigned int callbackHash(const void *key) {
@@ -68,7 +64,12 @@ static unsigned int callbackHash(const void *key) {
static void *callbackValDup(void *privdata, const void *src) {
((void) privdata);
- redisCallback *dup = malloc(sizeof(*dup));
+ redisCallback *dup;
+
+ dup = hi_malloc(sizeof(*dup));
+ if (dup == NULL)
+ return NULL;
+
memcpy(dup,src,sizeof(*dup));
return dup;
}
@@ -90,7 +91,7 @@ static void callbackKeyDestructor(void *privdata, void *key) {
static void callbackValDestructor(void *privdata, void *val) {
((void) privdata);
- free(val);
+ hi_free(val);
}
static dictType callbackDict = {
@@ -104,10 +105,19 @@ static dictType callbackDict = {
static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
redisAsyncContext *ac;
+ dict *channels = NULL, *patterns = NULL;
+
+ channels = dictCreate(&callbackDict,NULL);
+ if (channels == NULL)
+ goto oom;
+
+ patterns = dictCreate(&callbackDict,NULL);
+ if (patterns == NULL)
+ goto oom;
- ac = realloc(c,sizeof(redisAsyncContext));
+ ac = hi_realloc(c,sizeof(redisAsyncContext));
if (ac == NULL)
- return NULL;
+ goto oom;
c = &(ac->c);
@@ -119,6 +129,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
ac->err = 0;
ac->errstr = NULL;
ac->data = NULL;
+ ac->dataCleanup = NULL;
ac->ev.data = NULL;
ac->ev.addRead = NULL;
@@ -126,17 +137,25 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
ac->ev.addWrite = NULL;
ac->ev.delWrite = NULL;
ac->ev.cleanup = NULL;
+ ac->ev.scheduleTimer = NULL;
ac->onConnect = NULL;
+ ac->onConnectNC = NULL;
ac->onDisconnect = NULL;
ac->replies.head = NULL;
ac->replies.tail = NULL;
- ac->sub.invalid.head = NULL;
- ac->sub.invalid.tail = NULL;
- ac->sub.channels = dictCreate(&callbackDict,NULL);
- ac->sub.patterns = dictCreate(&callbackDict,NULL);
+ ac->sub.replies.head = NULL;
+ ac->sub.replies.tail = NULL;
+ ac->sub.channels = channels;
+ ac->sub.patterns = patterns;
+ ac->sub.pending_unsubs = 0;
+
return ac;
+oom:
+ if (channels) dictRelease(channels);
+ if (patterns) dictRelease(patterns);
+ return NULL;
}
/* We want the error field to be accessible directly instead of requiring
@@ -150,13 +169,21 @@ static void __redisAsyncCopyError(redisAsyncContext *ac) {
ac->errstr = c->errstr;
}
-redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
+redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {
+ redisOptions myOptions = *options;
redisContext *c;
redisAsyncContext *ac;
- c = redisConnectNonBlock(ip,port);
- if (c == NULL)
+ /* Clear any erroneously set sync callback and flag that we don't want to
+ * use freeReplyObject by default. */
+ myOptions.push_cb = NULL;
+ myOptions.options |= REDIS_OPT_NO_PUSH_AUTOFREE;
+
+ myOptions.options |= REDIS_OPT_NONBLOCK;
+ c = redisConnectWithOptions(&myOptions);
+ if (c == NULL) {
return NULL;
+ }
ac = redisAsyncInitialize(c);
if (ac == NULL) {
@@ -164,55 +191,70 @@ redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
return NULL;
}
+ /* Set any configured async push handler */
+ redisAsyncSetPushCallback(ac, myOptions.async_push_cb);
+
__redisAsyncCopyError(ac);
return ac;
}
+redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ return redisAsyncConnectWithOptions(&options);
+}
+
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
const char *source_addr) {
- redisContext *c = redisConnectBindNonBlock(ip,port,source_addr);
- redisAsyncContext *ac = redisAsyncInitialize(c);
- __redisAsyncCopyError(ac);
- return ac;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.endpoint.tcp.source_addr = source_addr;
+ return redisAsyncConnectWithOptions(&options);
}
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
const char *source_addr) {
- redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr);
- redisAsyncContext *ac = redisAsyncInitialize(c);
- __redisAsyncCopyError(ac);
- return ac;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.options |= REDIS_OPT_REUSEADDR;
+ options.endpoint.tcp.source_addr = source_addr;
+ return redisAsyncConnectWithOptions(&options);
}
redisAsyncContext *redisAsyncConnectUnix(const char *path) {
- redisContext *c;
- redisAsyncContext *ac;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ return redisAsyncConnectWithOptions(&options);
+}
- c = redisConnectUnixNonBlock(path);
- if (c == NULL)
- return NULL;
+static int
+redisAsyncSetConnectCallbackImpl(redisAsyncContext *ac, redisConnectCallback *fn,
+ redisConnectCallbackNC *fn_nc)
+{
+ /* If either are already set, this is an error */
+ if (ac->onConnect || ac->onConnectNC)
+ return REDIS_ERR;
- ac = redisAsyncInitialize(c);
- if (ac == NULL) {
- redisFree(c);
- return NULL;
+ if (fn) {
+ ac->onConnect = fn;
+ } else if (fn_nc) {
+ ac->onConnectNC = fn_nc;
}
- __redisAsyncCopyError(ac);
- return ac;
+ /* The common way to detect an established connection is to wait for
+ * the first write event to be fired. This assumes the related event
+ * library functions are already set. */
+ _EL_ADD_WRITE(ac);
+
+ return REDIS_OK;
}
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
- if (ac->onConnect == NULL) {
- ac->onConnect = fn;
+ return redisAsyncSetConnectCallbackImpl(ac, fn, NULL);
+}
- /* The common way to detect an established connection is to wait for
- * the first write event to be fired. This assumes the related event
- * library functions are already set. */
- _EL_ADD_WRITE(ac);
- return REDIS_OK;
- }
- return REDIS_ERR;
+int redisAsyncSetConnectCallbackNC(redisAsyncContext *ac, redisConnectCallbackNC *fn) {
+ return redisAsyncSetConnectCallbackImpl(ac, NULL, fn);
}
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
@@ -228,7 +270,7 @@ static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
redisCallback *cb;
/* Copy callback from stack to heap */
- cb = malloc(sizeof(*cb));
+ cb = hi_malloc(sizeof(*cb));
if (cb == NULL)
return REDIS_ERR_OOM;
@@ -256,7 +298,7 @@ static int __redisShiftCallback(redisCallbackList *list, redisCallback *target)
/* Copy callback from heap to stack */
if (target != NULL)
memcpy(target,cb,sizeof(*cb));
- free(cb);
+ hi_free(cb);
return REDIS_OK;
}
return REDIS_ERR;
@@ -271,46 +313,95 @@ static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisRe
}
}
+static void __redisRunPushCallback(redisAsyncContext *ac, redisReply *reply) {
+ if (ac->push_cb != NULL) {
+ ac->c.flags |= REDIS_IN_CALLBACK;
+ ac->push_cb(ac, reply);
+ ac->c.flags &= ~REDIS_IN_CALLBACK;
+ }
+}
+
+static void __redisRunConnectCallback(redisAsyncContext *ac, int status)
+{
+ if (ac->onConnect == NULL && ac->onConnectNC == NULL)
+ return;
+
+ if (!(ac->c.flags & REDIS_IN_CALLBACK)) {
+ ac->c.flags |= REDIS_IN_CALLBACK;
+ if (ac->onConnect) {
+ ac->onConnect(ac, status);
+ } else {
+ ac->onConnectNC(ac, status);
+ }
+ ac->c.flags &= ~REDIS_IN_CALLBACK;
+ } else {
+ /* already in callback */
+ if (ac->onConnect) {
+ ac->onConnect(ac, status);
+ } else {
+ ac->onConnectNC(ac, status);
+ }
+ }
+}
+
+static void __redisRunDisconnectCallback(redisAsyncContext *ac, int status)
+{
+ if (ac->onDisconnect) {
+ if (!(ac->c.flags & REDIS_IN_CALLBACK)) {
+ ac->c.flags |= REDIS_IN_CALLBACK;
+ ac->onDisconnect(ac, status);
+ ac->c.flags &= ~REDIS_IN_CALLBACK;
+ } else {
+ /* already in callback */
+ ac->onDisconnect(ac, status);
+ }
+ }
+}
+
/* Helper function to free the context. */
static void __redisAsyncFree(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
redisCallback cb;
- dictIterator *it;
+ dictIterator it;
dictEntry *de;
/* Execute pending callbacks with NULL reply. */
while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK)
__redisRunCallback(ac,&cb,NULL);
-
- /* Execute callbacks for invalid commands */
- while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
+ while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK)
__redisRunCallback(ac,&cb,NULL);
- /* Run subscription callbacks callbacks with NULL reply */
- it = dictGetIterator(ac->sub.channels);
- while ((de = dictNext(it)) != NULL)
- __redisRunCallback(ac,dictGetEntryVal(de),NULL);
- dictReleaseIterator(it);
- dictRelease(ac->sub.channels);
+ /* Run subscription callbacks with NULL reply */
+ if (ac->sub.channels) {
+ dictInitIterator(&it,ac->sub.channels);
+ while ((de = dictNext(&it)) != NULL)
+ __redisRunCallback(ac,dictGetEntryVal(de),NULL);
+
+ dictRelease(ac->sub.channels);
+ }
+
+ if (ac->sub.patterns) {
+ dictInitIterator(&it,ac->sub.patterns);
+ while ((de = dictNext(&it)) != NULL)
+ __redisRunCallback(ac,dictGetEntryVal(de),NULL);
- it = dictGetIterator(ac->sub.patterns);
- while ((de = dictNext(it)) != NULL)
- __redisRunCallback(ac,dictGetEntryVal(de),NULL);
- dictReleaseIterator(it);
- dictRelease(ac->sub.patterns);
+ dictRelease(ac->sub.patterns);
+ }
/* Signal event lib to clean up */
_EL_CLEANUP(ac);
/* Execute disconnect callback. When redisAsyncFree() initiated destroying
* this context, the status will always be REDIS_OK. */
- if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) {
- if (c->flags & REDIS_FREEING) {
- ac->onDisconnect(ac,REDIS_OK);
- } else {
- c->flags |= REDIS_FREEING;
- ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR);
- }
+ if (c->flags & REDIS_CONNECTED) {
+ int status = ac->err == 0 ? REDIS_OK : REDIS_ERR;
+ if (c->flags & REDIS_FREEING)
+ status = REDIS_OK;
+ __redisRunDisconnectCallback(ac, status);
+ }
+
+ if (ac->dataCleanup) {
+ ac->dataCleanup(ac->data);
}
/* Cleanup self */
@@ -322,14 +413,18 @@ static void __redisAsyncFree(redisAsyncContext *ac) {
* free'ing. To do so, a flag is set on the context which is picked up by
* redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
void redisAsyncFree(redisAsyncContext *ac) {
+ if (ac == NULL)
+ return;
+
redisContext *c = &(ac->c);
+
c->flags |= REDIS_FREEING;
if (!(c->flags & REDIS_IN_CALLBACK))
__redisAsyncFree(ac);
}
/* Helper function to make the disconnect happen and clean up. */
-static void __redisAsyncDisconnect(redisAsyncContext *ac) {
+void __redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
/* Make sure error is accessible if there is any */
@@ -337,16 +432,23 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
if (ac->err == 0) {
/* For clean disconnects, there should be no pending callbacks. */
- assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
+ int ret = __redisShiftCallback(&ac->replies,NULL);
+ assert(ret == REDIS_ERR);
} else {
/* Disconnection is caused by an error, make sure that pending
* callbacks cannot call new commands. */
c->flags |= REDIS_DISCONNECTING;
}
+ /* cleanup event library on disconnect.
+ * this is safe to call multiple times */
+ _EL_CLEANUP(ac);
+
/* For non-clean disconnects, __redisAsyncFree() will execute pending
* callbacks with a NULL-reply. */
- __redisAsyncFree(ac);
+ if (!(c->flags & REDIS_NO_AUTO_FREE)) {
+ __redisAsyncFree(ac);
+ }
}
/* Tries to do a clean disconnect from Redis, meaning it stops new commands
@@ -358,6 +460,9 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
void redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
c->flags |= REDIS_DISCONNECTING;
+
+ /** unset the auto-free flag here, because disconnect undoes this */
+ c->flags &= ~REDIS_NO_AUTO_FREE;
if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
__redisAsyncDisconnect(ac);
}
@@ -365,15 +470,17 @@ void redisAsyncDisconnect(redisAsyncContext *ac) {
static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
redisContext *c = &(ac->c);
dict *callbacks;
+ redisCallback *cb = NULL;
dictEntry *de;
int pvariant;
char *stype;
- sds sname;
+ sds sname = NULL;
- /* Custom reply functions are not supported for pub/sub. This will fail
- * very hard when they are used... */
- if (reply->type == REDIS_REPLY_ARRAY) {
- assert(reply->elements >= 2);
+ /* Match reply with the expected format of a pushed message.
+ * The type and number of elements (3 to 4) are specified at:
+ * https://redis.io/topics/pubsub#format-of-pushed-messages */
+ if ((reply->type == REDIS_REPLY_ARRAY && !(c->flags & REDIS_SUPPORTS_PUSH) && reply->elements >= 3) ||
+ reply->type == REDIS_REPLY_PUSH) {
assert(reply->element[0]->type == REDIS_REPLY_STRING);
stype = reply->element[0]->str;
pvariant = (tolower(stype[0]) == 'p') ? 1 : 0;
@@ -384,34 +491,84 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
callbacks = ac->sub.channels;
/* Locate the right callback */
- assert(reply->element[1]->type == REDIS_REPLY_STRING);
- sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
- de = dictFind(callbacks,sname);
- if (de != NULL) {
- memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb));
-
- /* If this is an unsubscribe message, remove it. */
- if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
+ if (reply->element[1]->type == REDIS_REPLY_STRING) {
+ sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
+ if (sname == NULL) goto oom;
+
+ if ((de = dictFind(callbacks,sname)) != NULL) {
+ cb = dictGetEntryVal(de);
+ memcpy(dstcb,cb,sizeof(*dstcb));
+ }
+ }
+
+ /* If this is an subscribe reply decrease pending counter. */
+ if (strcasecmp(stype+pvariant,"subscribe") == 0) {
+ assert(cb != NULL);
+ cb->pending_subs -= 1;
+
+ } else if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
+ if (cb == NULL)
+ ac->sub.pending_unsubs -= 1;
+ else if (cb->pending_subs == 0)
dictDelete(callbacks,sname);
- /* If this was the last unsubscribe message, revert to
- * non-subscribe mode. */
- assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
- if (reply->element[2]->integer == 0)
- c->flags &= ~REDIS_SUBSCRIBED;
+ /* If this was the last unsubscribe message, revert to
+ * non-subscribe mode. */
+ assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
+
+ /* Unset subscribed flag only when no pipelined pending subscribe
+ * or pending unsubscribe replies. */
+ if (reply->element[2]->integer == 0
+ && dictSize(ac->sub.channels) == 0
+ && dictSize(ac->sub.patterns) == 0
+ && ac->sub.pending_unsubs == 0) {
+ c->flags &= ~REDIS_SUBSCRIBED;
+
+ /* Move ongoing regular command callbacks. */
+ redisCallback cb;
+ while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK) {
+ __redisPushCallback(&ac->replies,&cb);
+ }
}
}
sdsfree(sname);
} else {
- /* Shift callback for invalid commands. */
- __redisShiftCallback(&ac->sub.invalid,dstcb);
+ /* Shift callback for pending command in subscribed context. */
+ __redisShiftCallback(&ac->sub.replies,dstcb);
}
return REDIS_OK;
+oom:
+ __redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory");
+ __redisAsyncCopyError(ac);
+ return REDIS_ERR;
+}
+
+#define redisIsSpontaneousPushReply(r) \
+ (redisIsPushReply(r) && !redisIsSubscribeReply(r))
+
+static int redisIsSubscribeReply(redisReply *reply) {
+ char *str;
+ size_t len, off;
+
+ /* We will always have at least one string with the subscribe/message type */
+ if (reply->elements < 1 || reply->element[0]->type != REDIS_REPLY_STRING ||
+ reply->element[0]->len < sizeof("message") - 1)
+ {
+ return 0;
+ }
+
+ /* Get the string/len moving past 'p' if needed */
+ off = tolower(reply->element[0]->str[0]) == 'p';
+ str = reply->element[0]->str + off;
+ len = reply->element[0]->len - off;
+
+ return !strncasecmp(str, "subscribe", len) ||
+ !strncasecmp(str, "message", len) ||
+ !strncasecmp(str, "unsubscribe", len);
}
void redisProcessCallbacks(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
- redisCallback cb = {NULL, NULL, NULL};
void *reply = NULL;
int status;
@@ -424,19 +581,27 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
__redisAsyncDisconnect(ac);
return;
}
-
- /* If monitor mode, repush callback */
- if(c->flags & REDIS_MONITORING) {
- __redisPushCallback(&ac->replies,&cb);
- }
-
/* When the connection is not being disconnected, simply stop
* trying to get replies and wait for the next loop tick. */
break;
}
- /* Even if the context is subscribed, pending regular callbacks will
- * get a reply before pub/sub messages arrive. */
+ /* Keep track of push message support for subscribe handling */
+ if (redisIsPushReply(reply)) c->flags |= REDIS_SUPPORTS_PUSH;
+
+ /* Send any non-subscribe related PUSH messages to our PUSH handler
+ * while allowing subscribe related PUSH messages to pass through.
+ * This allows existing code to be backward compatible and work in
+ * either RESP2 or RESP3 mode. */
+ if (redisIsSpontaneousPushReply(reply)) {
+ __redisRunPushCallback(ac, reply);
+ c->reader->fn->freeObject(reply);
+ continue;
+ }
+
+ /* Even if the context is subscribed, pending regular
+ * callbacks will get a reply before pub/sub messages arrive. */
+ redisCallback cb = {NULL, NULL, 0, 0, NULL};
if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
/*
* A spontaneous reply in a not-subscribed context can be the error
@@ -460,15 +625,17 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
__redisAsyncDisconnect(ac);
return;
}
- /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */
- assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING));
- if(c->flags & REDIS_SUBSCRIBED)
+ /* No more regular callbacks and no errors, the context *must* be subscribed. */
+ assert(c->flags & REDIS_SUBSCRIBED);
+ if (c->flags & REDIS_SUBSCRIBED)
__redisGetSubscribeCallback(ac,reply,&cb);
}
if (cb.fn != NULL) {
__redisRunCallback(ac,&cb,reply);
- c->reader->fn->freeObject(reply);
+ if (!(c->flags & REDIS_NO_AUTO_FREE_REPLIES)){
+ c->reader->fn->freeObject(reply);
+ }
/* Proceed with free'ing when redisAsyncFree() was called. */
if (c->flags & REDIS_FREEING) {
@@ -481,11 +648,11 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
* abort with an error, but simply ignore it because the client
* doesn't know what the server will spit out over the wire. */
c->reader->fn->freeObject(reply);
- /* Proceed with free'ing when redisAsyncFree() was called. */
- if (c->flags & REDIS_FREEING) {
- __redisAsyncFree(ac);
- return;
- }
+ }
+
+ /* If in monitor mode, repush the callback */
+ if (c->flags & REDIS_MONITORING) {
+ __redisPushCallback(&ac->replies,&cb);
}
}
@@ -494,26 +661,61 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
__redisAsyncDisconnect(ac);
}
+static void __redisAsyncHandleConnectFailure(redisAsyncContext *ac) {
+ __redisRunConnectCallback(ac, REDIS_ERR);
+ __redisAsyncDisconnect(ac);
+}
+
/* Internal helper function to detect socket status the first time a read or
* write event fires. When connecting was not successful, the connect callback
* is called with a REDIS_ERR status and the context is free'd. */
static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
+ int completed = 0;
redisContext *c = &(ac->c);
- if (redisCheckSocketError(c) == REDIS_ERR) {
- /* Try again later when connect(2) is still in progress. */
- if (errno == EINPROGRESS)
- return REDIS_OK;
-
- if (ac->onConnect) ac->onConnect(ac,REDIS_ERR);
- __redisAsyncDisconnect(ac);
+ if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {
+ /* Error! */
+ if (redisCheckSocketError(c) == REDIS_ERR)
+ __redisAsyncCopyError(ac);
+ __redisAsyncHandleConnectFailure(ac);
return REDIS_ERR;
+ } else if (completed == 1) {
+ /* connected! */
+ if (c->connection_type == REDIS_CONN_TCP &&
+ redisSetTcpNoDelay(c) == REDIS_ERR) {
+ __redisAsyncHandleConnectFailure(ac);
+ return REDIS_ERR;
+ }
+
+ /* flag us as fully connect, but allow the callback
+ * to disconnect. For that reason, permit the function
+ * to delete the context here after callback return.
+ */
+ c->flags |= REDIS_CONNECTED;
+ __redisRunConnectCallback(ac, REDIS_OK);
+ if ((ac->c.flags & REDIS_DISCONNECTING)) {
+ redisAsyncDisconnect(ac);
+ return REDIS_ERR;
+ } else if ((ac->c.flags & REDIS_FREEING)) {
+ redisAsyncFree(ac);
+ return REDIS_ERR;
+ }
+ return REDIS_OK;
+ } else {
+ return REDIS_OK;
}
+}
- /* Mark context as connected. */
- c->flags |= REDIS_CONNECTED;
- if (ac->onConnect) ac->onConnect(ac,REDIS_OK);
- return REDIS_OK;
+void redisAsyncRead(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+
+ if (redisBufferRead(c) == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ } else {
+ /* Always re-schedule reads */
+ _EL_ADD_READ(ac);
+ redisProcessCallbacks(ac);
+ }
}
/* This function should be called when the socket is readable.
@@ -521,6 +723,8 @@ static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
*/
void redisAsyncHandleRead(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
+ /* must not be called from a callback */
+ assert(!(c->flags & REDIS_IN_CALLBACK));
if (!(c->flags & REDIS_CONNECTED)) {
/* Abort connect was not successful. */
@@ -531,18 +735,31 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
return;
}
- if (redisBufferRead(c) == REDIS_ERR) {
+ c->funcs->async_read(ac);
+}
+
+void redisAsyncWrite(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ int done = 0;
+
+ if (redisBufferWrite(c,&done) == REDIS_ERR) {
__redisAsyncDisconnect(ac);
} else {
- /* Always re-schedule reads */
+ /* Continue writing when not done, stop writing otherwise */
+ if (!done)
+ _EL_ADD_WRITE(ac);
+ else
+ _EL_DEL_WRITE(ac);
+
+ /* Always schedule reads after writes */
_EL_ADD_READ(ac);
- redisProcessCallbacks(ac);
}
}
void redisAsyncHandleWrite(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
- int done = 0;
+ /* must not be called from a callback */
+ assert(!(c->flags & REDIS_IN_CALLBACK));
if (!(c->flags & REDIS_CONNECTED)) {
/* Abort connect was not successful. */
@@ -553,18 +770,46 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
return;
}
- if (redisBufferWrite(c,&done) == REDIS_ERR) {
- __redisAsyncDisconnect(ac);
- } else {
- /* Continue writing when not done, stop writing otherwise */
- if (!done)
- _EL_ADD_WRITE(ac);
- else
- _EL_DEL_WRITE(ac);
+ c->funcs->async_write(ac);
+}
- /* Always schedule reads after writes */
- _EL_ADD_READ(ac);
+void redisAsyncHandleTimeout(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ redisCallback cb;
+ /* must not be called from a callback */
+ assert(!(c->flags & REDIS_IN_CALLBACK));
+
+ if ((c->flags & REDIS_CONNECTED)) {
+ if (ac->replies.head == NULL && ac->sub.replies.head == NULL) {
+ /* Nothing to do - just an idle timeout */
+ return;
+ }
+
+ if (!ac->c.command_timeout ||
+ (!ac->c.command_timeout->tv_sec && !ac->c.command_timeout->tv_usec)) {
+ /* A belated connect timeout arriving, ignore */
+ return;
+ }
}
+
+ if (!c->err) {
+ __redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout");
+ __redisAsyncCopyError(ac);
+ }
+
+ if (!(c->flags & REDIS_CONNECTED)) {
+ __redisRunConnectCallback(ac, REDIS_ERR);
+ }
+
+ while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) {
+ __redisRunCallback(ac, &cb, NULL);
+ }
+
+ /**
+ * TODO: Don't automatically sever the connection,
+ * rather, allow to ignore <x> responses before the queue is clear
+ */
+ __redisAsyncDisconnect(ac);
}
/* Sets a pointer to the first argument and its length starting at p. Returns
@@ -589,6 +834,10 @@ static const char *nextArgument(const char *start, const char **str, size_t *len
static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
redisContext *c = &(ac->c);
redisCallback cb;
+ struct dict *cbdict;
+ dictIterator it;
+ dictEntry *de;
+ redisCallback *existcb;
int pvariant, hasnext;
const char *cstr, *astr;
size_t clen, alen;
@@ -602,6 +851,8 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
/* Setup callback */
cb.fn = fn;
cb.privdata = privdata;
+ cb.pending_subs = 1;
+ cb.unsubscribe_sent = 0;
/* Find out which command will be appended. */
p = nextArgument(cmd,&cstr,&clen);
@@ -611,38 +862,97 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
cstr += pvariant;
clen -= pvariant;
- if (hasnext && clen >= 9 && strncasecmp(cstr,"subscribe\r\n",9) == 0) {
+ if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) {
c->flags |= REDIS_SUBSCRIBED;
/* Add every channel/pattern to the list of subscription callbacks. */
while ((p = nextArgument(p,&astr,&alen)) != NULL) {
sname = sdsnewlen(astr,alen);
+ if (sname == NULL)
+ goto oom;
+
if (pvariant)
- ret = dictReplace(ac->sub.patterns,sname,&cb);
+ cbdict = ac->sub.patterns;
else
- ret = dictReplace(ac->sub.channels,sname,&cb);
+ cbdict = ac->sub.channels;
+
+ de = dictFind(cbdict,sname);
+
+ if (de != NULL) {
+ existcb = dictGetEntryVal(de);
+ cb.pending_subs = existcb->pending_subs + 1;
+ }
+
+ ret = dictReplace(cbdict,sname,&cb);
if (ret == 0) sdsfree(sname);
}
- } else if (clen >= 11 && strncasecmp(cstr,"unsubscribe\r\n",11) == 0) {
+ } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
/* It is only useful to call (P)UNSUBSCRIBE when the context is
* subscribed to one or more channels or patterns. */
if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR;
+ if (pvariant)
+ cbdict = ac->sub.patterns;
+ else
+ cbdict = ac->sub.channels;
+
+ if (hasnext) {
+ /* Send an unsubscribe with specific channels/patterns.
+ * Bookkeeping the number of expected replies */
+ while ((p = nextArgument(p,&astr,&alen)) != NULL) {
+ sname = sdsnewlen(astr,alen);
+ if (sname == NULL)
+ goto oom;
+
+ de = dictFind(cbdict,sname);
+ if (de != NULL) {
+ existcb = dictGetEntryVal(de);
+ if (existcb->unsubscribe_sent == 0)
+ existcb->unsubscribe_sent = 1;
+ else
+ /* Already sent, reply to be ignored */
+ ac->sub.pending_unsubs += 1;
+ } else {
+ /* Not subscribed to, reply to be ignored */
+ ac->sub.pending_unsubs += 1;
+ }
+ sdsfree(sname);
+ }
+ } else {
+ /* Send an unsubscribe without specific channels/patterns.
+ * Bookkeeping the number of expected replies */
+ int no_subs = 1;
+ dictInitIterator(&it,cbdict);
+ while ((de = dictNext(&it)) != NULL) {
+ existcb = dictGetEntryVal(de);
+ if (existcb->unsubscribe_sent == 0) {
+ existcb->unsubscribe_sent = 1;
+ no_subs = 0;
+ }
+ }
+ /* Unsubscribing to all channels/patterns, where none is
+ * subscribed to, results in a single reply to be ignored. */
+ if (no_subs == 1)
+ ac->sub.pending_unsubs += 1;
+ }
+
/* (P)UNSUBSCRIBE does not have its own response: every channel or
* pattern that is unsubscribed will receive a message. This means we
* should not append a callback function for this command. */
- } else if(clen >= 7 && strncasecmp(cstr,"monitor\r\n",7) == 0) {
- /* Set monitor flag and push callback */
- c->flags |= REDIS_MONITORING;
- __redisPushCallback(&ac->replies,&cb);
+ } else if (strncasecmp(cstr,"monitor\r\n",9) == 0) {
+ /* Set monitor flag and push callback */
+ c->flags |= REDIS_MONITORING;
+ if (__redisPushCallback(&ac->replies,&cb) != REDIS_OK)
+ goto oom;
} else {
- if (c->flags & REDIS_SUBSCRIBED)
- /* This will likely result in an error reply, but it needs to be
- * received and passed to the callback. */
- __redisPushCallback(&ac->sub.invalid,&cb);
- else
- __redisPushCallback(&ac->replies,&cb);
+ if (c->flags & REDIS_SUBSCRIBED) {
+ if (__redisPushCallback(&ac->sub.replies,&cb) != REDIS_OK)
+ goto oom;
+ } else {
+ if (__redisPushCallback(&ac->replies,&cb) != REDIS_OK)
+ goto oom;
+ }
}
__redisAppendCommand(c,cmd,len);
@@ -651,6 +961,10 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
_EL_ADD_WRITE(ac);
return REDIS_OK;
+oom:
+ __redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory");
+ __redisAsyncCopyError(ac);
+ return REDIS_ERR;
}
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
@@ -664,7 +978,7 @@ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdat
return REDIS_ERR;
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
- free(cmd);
+ hi_free(cmd);
return status;
}
@@ -679,9 +993,11 @@ int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
sds cmd;
- int len;
+ long long len;
int status;
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
+ if (len < 0)
+ return REDIS_ERR;
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
sdsfree(cmd);
return status;
@@ -691,3 +1007,28 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
return status;
}
+
+redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn) {
+ redisAsyncPushFn *old = ac->push_cb;
+ ac->push_cb = fn;
+ return old;
+}
+
+int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {
+ if (!ac->c.command_timeout) {
+ ac->c.command_timeout = hi_calloc(1, sizeof(tv));
+ if (ac->c.command_timeout == NULL) {
+ __redisSetError(&ac->c, REDIS_ERR_OOM, "Out of memory");
+ __redisAsyncCopyError(ac);
+ return REDIS_ERR;
+ }
+ }
+
+ if (tv.tv_sec != ac->c.command_timeout->tv_sec ||
+ tv.tv_usec != ac->c.command_timeout->tv_usec)
+ {
+ *ac->c.command_timeout = tv;
+ }
+
+ return REDIS_OK;
+}
diff --git a/contrib/hiredis/async.h b/contrib/hiredis/async.h
index f19139c6f..4f94660b1 100644
--- a/contrib/hiredis/async.h
+++ b/contrib/hiredis/async.h
@@ -45,6 +45,8 @@ typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
typedef struct redisCallback {
struct redisCallback *next; /* simple singly linked list */
redisCallbackFn *fn;
+ int pending_subs;
+ int unsubscribe_sent;
void *privdata;
} redisCallback;
@@ -56,6 +58,8 @@ typedef struct redisCallbackList {
/* Connection callback prototypes */
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
+typedef void (redisConnectCallbackNC)(struct redisAsyncContext *, int status);
+typedef void(redisTimerCallback)(void *timer, void *privdata);
/* Context for an async connection to Redis */
typedef struct redisAsyncContext {
@@ -68,6 +72,7 @@ typedef struct redisAsyncContext {
/* Not used by hiredis */
void *data;
+ void (*dataCleanup)(void *privdata);
/* Event library data and hooks */
struct {
@@ -80,6 +85,7 @@ typedef struct redisAsyncContext {
void (*addWrite)(void *privdata);
void (*delWrite)(void *privdata);
void (*cleanup)(void *privdata);
+ void (*scheduleTimer)(void *privdata, struct timeval tv);
} ev;
/* Called when either the connection is terminated due to an error or per
@@ -88,42 +94,53 @@ typedef struct redisAsyncContext {
/* Called when the first write event was received. */
redisConnectCallback *onConnect;
+ redisConnectCallbackNC *onConnectNC;
/* Regular command callbacks */
redisCallbackList replies;
+ /* Address used for connect() */
+ struct sockaddr *saddr;
+ size_t addrlen;
+
/* Subscription callbacks */
struct {
- redisCallbackList invalid;
+ redisCallbackList replies;
struct dict *channels;
struct dict *patterns;
+ int pending_unsubs;
} sub;
+
+ /* Any configured RESP3 PUSH handler */
+ redisAsyncPushFn *push_cb;
} redisAsyncContext;
/* Functions that proxy to hiredis */
+redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options);
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
const char *source_addr);
redisAsyncContext *redisAsyncConnectUnix(const char *path);
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
+int redisAsyncSetConnectCallbackNC(redisAsyncContext *ac, redisConnectCallbackNC *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
+
+redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn);
+int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);
void redisAsyncDisconnect(redisAsyncContext *ac);
void redisAsyncFree(redisAsyncContext *ac);
/* Handle read/write events */
void redisAsyncHandleRead(redisAsyncContext *ac);
void redisAsyncHandleWrite(redisAsyncContext *ac);
+void redisAsyncHandleTimeout(redisAsyncContext *ac);
+void redisAsyncRead(redisAsyncContext *ac);
+void redisAsyncWrite(redisAsyncContext *ac);
/* Command functions for an async context. Write the command to the
* output buffer and register the provided callback. */
-#ifdef __GNUC__
-__attribute__((format(printf, 4, 0)))
-#endif
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap);
-#ifdef __GNUC__
-__attribute__((format(printf, 4, 5)))
-#endif
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len);
diff --git a/contrib/hiredis/async_private.h b/contrib/hiredis/async_private.h
new file mode 100644
index 000000000..ea0558d42
--- /dev/null
+++ b/contrib/hiredis/async_private.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_ASYNC_PRIVATE_H
+#define __HIREDIS_ASYNC_PRIVATE_H
+
+#define _EL_ADD_READ(ctx) \
+ do { \
+ refreshTimeout(ctx); \
+ if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
+ } while (0)
+#define _EL_DEL_READ(ctx) do { \
+ if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
+ } while(0)
+#define _EL_ADD_WRITE(ctx) \
+ do { \
+ refreshTimeout(ctx); \
+ if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
+ } while (0)
+#define _EL_DEL_WRITE(ctx) do { \
+ if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
+ } while(0)
+#define _EL_CLEANUP(ctx) do { \
+ if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
+ ctx->ev.cleanup = NULL; \
+ } while(0)
+
+static inline void refreshTimeout(redisAsyncContext *ctx) {
+ #define REDIS_TIMER_ISSET(tvp) \
+ (tvp && ((tvp)->tv_sec || (tvp)->tv_usec))
+
+ #define REDIS_EL_TIMER(ac, tvp) \
+ if ((ac)->ev.scheduleTimer && REDIS_TIMER_ISSET(tvp)) { \
+ (ac)->ev.scheduleTimer((ac)->ev.data, *(tvp)); \
+ }
+
+ if (ctx->c.flags & REDIS_CONNECTED) {
+ REDIS_EL_TIMER(ctx, ctx->c.command_timeout);
+ } else {
+ REDIS_EL_TIMER(ctx, ctx->c.connect_timeout);
+ }
+}
+
+void __redisAsyncDisconnect(redisAsyncContext *ac);
+void redisProcessCallbacks(redisAsyncContext *ac);
+
+#endif /* __HIREDIS_ASYNC_PRIVATE_H */
diff --git a/contrib/hiredis/dict.c b/contrib/hiredis/dict.c
index 0fbc1b4cf..ad571818e 100644
--- a/contrib/hiredis/dict.c
+++ b/contrib/hiredis/dict.c
@@ -34,6 +34,7 @@
*/
#include "fmacros.h"
+#include "alloc.h"
#include <stdlib.h>
#include <assert.h>
#include <limits.h>
@@ -71,7 +72,10 @@ static void _dictReset(dict *ht) {
/* Create a new hash table */
static dict *dictCreate(dictType *type, void *privDataPtr) {
- dict *ht = malloc(sizeof(*ht));
+ dict *ht = hi_malloc(sizeof(*ht));
+ if (ht == NULL)
+ return NULL;
+
_dictInit(ht,type,privDataPtr);
return ht;
}
@@ -97,7 +101,9 @@ static int dictExpand(dict *ht, unsigned long size) {
_dictInit(&n, ht->type, ht->privdata);
n.size = realsize;
n.sizemask = realsize-1;
- n.table = calloc(realsize,sizeof(dictEntry*));
+ n.table = hi_calloc(realsize,sizeof(dictEntry*));
+ if (n.table == NULL)
+ return DICT_ERR;
/* Copy all the elements from the old to the new table:
* note that if the old hash table is empty ht->size is zero,
@@ -124,7 +130,7 @@ static int dictExpand(dict *ht, unsigned long size) {
}
}
assert(ht->used == 0);
- free(ht->table);
+ hi_free(ht->table);
/* Remap the new hashtable in the old */
*ht = n;
@@ -142,7 +148,10 @@ static int dictAdd(dict *ht, void *key, void *val) {
return DICT_ERR;
/* Allocates the memory and stores key */
- entry = malloc(sizeof(*entry));
+ entry = hi_malloc(sizeof(*entry));
+ if (entry == NULL)
+ return DICT_ERR;
+
entry->next = ht->table[index];
ht->table[index] = entry;
@@ -166,17 +175,18 @@ static int dictReplace(dict *ht, void *key, void *val) {
return 1;
/* It already exists, get the entry */
entry = dictFind(ht, key);
+ if (entry == NULL)
+ return 0;
+
/* Free the old value and set the new one */
/* Set the new value and free the old one. Note that it is important
* to do that in this order, as the value may just be exactly the same
* as the previous one. In this context, think to reference counting,
* you want to increment (set), and then decrement (free), and not the
* reverse. */
- if (entry) {
- auxentry = *entry;
- dictSetHashVal(ht, entry, val);
- dictFreeEntryVal(ht, &auxentry);
- }
+ auxentry = *entry;
+ dictSetHashVal(ht, entry, val);
+ dictFreeEntryVal(ht, &auxentry);
return 0;
}
@@ -201,7 +211,7 @@ static int dictDelete(dict *ht, const void *key) {
dictFreeEntryKey(ht,de);
dictFreeEntryVal(ht,de);
- free(de);
+ hi_free(de);
ht->used--;
return DICT_OK;
}
@@ -224,13 +234,13 @@ static int _dictClear(dict *ht) {
nextHe = he->next;
dictFreeEntryKey(ht, he);
dictFreeEntryVal(ht, he);
- free(he);
+ hi_free(he);
ht->used--;
he = nextHe;
}
}
/* Free the table and the allocated cache structure */
- free(ht->table);
+ hi_free(ht->table);
/* Re-initialize the table */
_dictReset(ht);
return DICT_OK; /* never fails */
@@ -239,7 +249,7 @@ static int _dictClear(dict *ht) {
/* Clear & Release the hash table */
static void dictRelease(dict *ht) {
_dictClear(ht);
- free(ht);
+ hi_free(ht);
}
static dictEntry *dictFind(dict *ht, const void *key) {
@@ -257,14 +267,11 @@ static dictEntry *dictFind(dict *ht, const void *key) {
return NULL;
}
-static dictIterator *dictGetIterator(dict *ht) {
- dictIterator *iter = malloc(sizeof(*iter));
-
+static void dictInitIterator(dictIterator *iter, dict *ht) {
iter->ht = ht;
iter->index = -1;
iter->entry = NULL;
iter->nextEntry = NULL;
- return iter;
}
static dictEntry *dictNext(dictIterator *iter) {
@@ -287,16 +294,12 @@ static dictEntry *dictNext(dictIterator *iter) {
return NULL;
}
-static void dictReleaseIterator(dictIterator *iter) {
- free(iter);
-}
-
/* ------------------------- private functions ------------------------------ */
/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *ht) {
/* If the hash table is empty expand it to the initial size,
- * if the table is "full" dobule its size. */
+ * if the table is "full" double its size. */
if (ht->size == 0)
return dictExpand(ht, DICT_HT_INITIAL_SIZE);
if (ht->used == ht->size)
diff --git a/contrib/hiredis/dict.h b/contrib/hiredis/dict.h
index 95fcd280e..6ad0acd8d 100644
--- a/contrib/hiredis/dict.h
+++ b/contrib/hiredis/dict.h
@@ -119,8 +119,7 @@ static int dictReplace(dict *ht, void *key, void *val);
static int dictDelete(dict *ht, const void *key);
static void dictRelease(dict *ht);
static dictEntry * dictFind(dict *ht, const void *key);
-static dictIterator *dictGetIterator(dict *ht);
+static void dictInitIterator(dictIterator *iter, dict *ht);
static dictEntry *dictNext(dictIterator *iter);
-static void dictReleaseIterator(dictIterator *iter);
#endif /* __DICT_H */
diff --git a/contrib/hiredis/fmacros.h b/contrib/hiredis/fmacros.h
index 19d7b2193..754a53c21 100644
--- a/contrib/hiredis/fmacros.h
+++ b/contrib/hiredis/fmacros.h
@@ -1,21 +1,14 @@
#ifndef __HIREDIS_FMACRO_H
#define __HIREDIS_FMACRO_H
-#if defined(__linux__)
-#define _BSD_SOURCE
-#define _DEFAULT_SOURCE
-#endif
-
-#if defined(__sun__)
-#define _POSIX_C_SOURCE 200112L
-#elif defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__)
+#ifndef _AIX
#define _XOPEN_SOURCE 600
-#else
-#define _XOPEN_SOURCE
+#define _POSIX_C_SOURCE 200112L
#endif
-#if __APPLE__ && __MACH__
-#define _OSX
+#if defined(__APPLE__) && defined(__MACH__)
+/* Enable TCP_KEEPALIVE */
+#define _DARWIN_C_SOURCE
#endif
#endif
diff --git a/contrib/hiredis/hiredis.c b/contrib/hiredis/hiredis.c
index 0f87bc323..446ceb1e6 100644
--- a/contrib/hiredis/hiredis.c
+++ b/contrib/hiredis/hiredis.c
@@ -34,7 +34,6 @@
#include "fmacros.h"
#include <string.h>
#include <stdlib.h>
-#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
@@ -42,12 +41,28 @@
#include "hiredis.h"
#include "net.h"
#include "sds.h"
+#include "async.h"
+#include "win32.h"
+
+extern int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout);
+extern int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout);
+
+static redisContextFuncs redisContextDefaultFuncs = {
+ .close = redisNetClose,
+ .free_privctx = NULL,
+ .async_read = redisAsyncRead,
+ .async_write = redisAsyncWrite,
+ .read = redisNetRead,
+ .write = redisNetWrite
+};
static redisReply *createReplyObject(int type);
static void *createStringObject(const redisReadTask *task, char *str, size_t len);
-static void *createArrayObject(const redisReadTask *task, int elements);
+static void *createArrayObject(const redisReadTask *task, size_t elements);
static void *createIntegerObject(const redisReadTask *task, long long value);
+static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len);
static void *createNilObject(const redisReadTask *task);
+static void *createBoolObject(const redisReadTask *task, int bval);
/* Default set of functions to build the reply. Keep in mind that such a
* function returning NULL is interpreted as OOM. */
@@ -55,13 +70,15 @@ static redisReplyObjectFunctions defaultFunctions = {
createStringObject,
createArrayObject,
createIntegerObject,
+ createDoubleObject,
createNilObject,
+ createBoolObject,
freeReplyObject
};
/* Create a reply object */
static redisReply *createReplyObject(int type) {
- redisReply *r = calloc(1,sizeof(*r));
+ redisReply *r = hi_calloc(1,sizeof(*r));
if (r == NULL)
return NULL;
@@ -80,23 +97,29 @@ void freeReplyObject(void *reply) {
switch(r->type) {
case REDIS_REPLY_INTEGER:
+ case REDIS_REPLY_NIL:
+ case REDIS_REPLY_BOOL:
break; /* Nothing to free */
case REDIS_REPLY_ARRAY:
+ case REDIS_REPLY_MAP:
+ case REDIS_REPLY_SET:
+ case REDIS_REPLY_PUSH:
if (r->element != NULL) {
for (j = 0; j < r->elements; j++)
- if (r->element[j] != NULL)
- freeReplyObject(r->element[j]);
- free(r->element);
+ freeReplyObject(r->element[j]);
+ hi_free(r->element);
}
break;
case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS:
case REDIS_REPLY_STRING:
- if (r->str != NULL)
- free(r->str);
+ case REDIS_REPLY_DOUBLE:
+ case REDIS_REPLY_VERB:
+ case REDIS_REPLY_BIGNUM:
+ hi_free(r->str);
break;
}
- free(r);
+ hi_free(r);
}
static void *createStringObject(const redisReadTask *task, char *str, size_t len) {
@@ -107,39 +130,56 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
if (r == NULL)
return NULL;
- buf = malloc(len+1);
- if (buf == NULL) {
- freeReplyObject(r);
- return NULL;
- }
-
assert(task->type == REDIS_REPLY_ERROR ||
task->type == REDIS_REPLY_STATUS ||
- task->type == REDIS_REPLY_STRING);
+ task->type == REDIS_REPLY_STRING ||
+ task->type == REDIS_REPLY_VERB ||
+ task->type == REDIS_REPLY_BIGNUM);
/* Copy string value */
- memcpy(buf,str,len);
- buf[len] = '\0';
+ if (task->type == REDIS_REPLY_VERB) {
+ buf = hi_malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */
+ if (buf == NULL) goto oom;
+
+ memcpy(r->vtype,str,3);
+ r->vtype[3] = '\0';
+ memcpy(buf,str+4,len-4);
+ buf[len-4] = '\0';
+ r->len = len - 4;
+ } else {
+ buf = hi_malloc(len+1);
+ if (buf == NULL) goto oom;
+
+ memcpy(buf,str,len);
+ buf[len] = '\0';
+ r->len = len;
+ }
r->str = buf;
- r->len = len;
if (task->parent) {
parent = task->parent->obj;
- assert(parent->type == REDIS_REPLY_ARRAY);
+ assert(parent->type == REDIS_REPLY_ARRAY ||
+ parent->type == REDIS_REPLY_MAP ||
+ parent->type == REDIS_REPLY_SET ||
+ parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r;
}
return r;
+
+oom:
+ freeReplyObject(r);
+ return NULL;
}
-static void *createArrayObject(const redisReadTask *task, int elements) {
+static void *createArrayObject(const redisReadTask *task, size_t elements) {
redisReply *r, *parent;
- r = createReplyObject(REDIS_REPLY_ARRAY);
+ r = createReplyObject(task->type);
if (r == NULL)
return NULL;
if (elements > 0) {
- r->element = calloc(elements,sizeof(redisReply*));
+ r->element = hi_calloc(elements,sizeof(redisReply*));
if (r->element == NULL) {
freeReplyObject(r);
return NULL;
@@ -150,7 +190,10 @@ static void *createArrayObject(const redisReadTask *task, int elements) {
if (task->parent) {
parent = task->parent->obj;
- assert(parent->type == REDIS_REPLY_ARRAY);
+ assert(parent->type == REDIS_REPLY_ARRAY ||
+ parent->type == REDIS_REPLY_MAP ||
+ parent->type == REDIS_REPLY_SET ||
+ parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r;
}
return r;
@@ -167,7 +210,47 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
if (task->parent) {
parent = task->parent->obj;
- assert(parent->type == REDIS_REPLY_ARRAY);
+ assert(parent->type == REDIS_REPLY_ARRAY ||
+ parent->type == REDIS_REPLY_MAP ||
+ parent->type == REDIS_REPLY_SET ||
+ parent->type == REDIS_REPLY_PUSH);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) {
+ redisReply *r, *parent;
+
+ if (len == SIZE_MAX) // Prevents hi_malloc(0) if len equals to SIZE_MAX
+ return NULL;
+
+ r = createReplyObject(REDIS_REPLY_DOUBLE);
+ if (r == NULL)
+ return NULL;
+
+ r->dval = value;
+ r->str = hi_malloc(len+1);
+ if (r->str == NULL) {
+ freeReplyObject(r);
+ return NULL;
+ }
+
+ /* The double reply also has the original protocol string representing a
+ * double as a null terminated string. This way the caller does not need
+ * to format back for string conversion, especially since Redis does efforts
+ * to make the string more human readable avoiding the calssical double
+ * decimal string conversion artifacts. */
+ memcpy(r->str, str, len);
+ r->str[len] = '\0';
+ r->len = len;
+
+ if (task->parent) {
+ parent = task->parent->obj;
+ assert(parent->type == REDIS_REPLY_ARRAY ||
+ parent->type == REDIS_REPLY_MAP ||
+ parent->type == REDIS_REPLY_SET ||
+ parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r;
}
return r;
@@ -182,7 +265,30 @@ static void *createNilObject(const redisReadTask *task) {
if (task->parent) {
parent = task->parent->obj;
- assert(parent->type == REDIS_REPLY_ARRAY);
+ assert(parent->type == REDIS_REPLY_ARRAY ||
+ parent->type == REDIS_REPLY_MAP ||
+ parent->type == REDIS_REPLY_SET ||
+ parent->type == REDIS_REPLY_PUSH);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+static void *createBoolObject(const redisReadTask *task, int bval) {
+ redisReply *r, *parent;
+
+ r = createReplyObject(REDIS_REPLY_BOOL);
+ if (r == NULL)
+ return NULL;
+
+ r->integer = bval != 0;
+
+ if (task->parent) {
+ parent = task->parent->obj;
+ assert(parent->type == REDIS_REPLY_ARRAY ||
+ parent->type == REDIS_REPLY_MAP ||
+ parent->type == REDIS_REPLY_SET ||
+ parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r;
}
return r;
@@ -232,7 +338,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
if (*c != '%' || c[1] == '\0') {
if (*c == ' ') {
if (touched) {
- newargv = realloc(curargv,sizeof(char*)*(argc+1));
+ newargv = hi_realloc(curargv,sizeof(char*)*(argc+1));
if (newargv == NULL) goto memory_err;
curargv = newargv;
curargv[argc++] = curarg;
@@ -286,17 +392,22 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++;
/* Field width */
- while (*_p != '\0' && isdigit(*_p)) _p++;
+ while (*_p != '\0' && isdigit((int) *_p)) _p++;
/* Precision */
if (*_p == '.') {
_p++;
- while (*_p != '\0' && isdigit(*_p)) _p++;
+ while (*_p != '\0' && isdigit((int) *_p)) _p++;
}
/* Copy va_list before consuming with va_arg */
va_copy(_cpy,ap);
+ /* Make sure we have more characters otherwise strchr() accepts
+ * '\0' as an integer specifier. This is checked after above
+ * va_copy() to avoid UB in fmt_invalid's call to va_end(). */
+ if (*_p == '\0') goto fmt_invalid;
+
/* Integer conversion (without modifiers) */
if (strchr(intfmts,*_p) != NULL) {
va_arg(ap,int);
@@ -375,13 +486,15 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
touched = 1;
c++;
+ if (*c == '\0')
+ break;
}
c++;
}
/* Add the last argument if needed */
if (touched) {
- newargv = realloc(curargv,sizeof(char*)*(argc+1));
+ newargv = hi_realloc(curargv,sizeof(char*)*(argc+1));
if (newargv == NULL) goto memory_err;
curargv = newargv;
curargv[argc++] = curarg;
@@ -397,7 +510,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
totlen += 1+countDigits(argc)+2;
/* Build the command at protocol level */
- cmd = malloc(totlen+1);
+ cmd = hi_malloc(totlen+1);
if (cmd == NULL) goto memory_err;
pos = sprintf(cmd,"*%d\r\n",argc);
@@ -412,7 +525,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
assert(pos == totlen);
cmd[pos] = '\0';
- free(curargv);
+ hi_free(curargv);
*target = cmd;
return totlen;
@@ -428,15 +541,11 @@ cleanup:
if (curargv) {
while(argc--)
sdsfree(curargv[argc]);
- free(curargv);
+ hi_free(curargv);
}
sdsfree(curarg);
-
- /* No need to check cmd since it is the last statement that can fail,
- * but do it anyway to be as defensive as possible. */
- if (cmd != NULL)
- free(cmd);
+ hi_free(cmd);
return error_type;
}
@@ -474,13 +583,12 @@ int redisFormatCommand(char **target, const char *format, ...) {
* lengths. If the latter is set to NULL, strlen will be used to compute the
* argument lengths.
*/
-int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv,
- const size_t *argvlen)
+long long redisFormatSdsCommandArgv(sds *target, int argc, const char **argv,
+ const size_t *argvlen)
{
- sds cmd;
- unsigned long long totlen;
+ sds cmd, aux;
+ unsigned long long totlen, len;
int j;
- size_t len;
/* Abort on a NULL target */
if (target == NULL)
@@ -499,15 +607,19 @@ int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv,
return -1;
/* We already know how much storage we need */
- cmd = sdsMakeRoomFor(cmd, totlen);
- if (cmd == NULL)
+ aux = sdsMakeRoomFor(cmd, totlen);
+ if (aux == NULL) {
+ sdsfree(cmd);
return -1;
+ }
+
+ cmd = aux;
/* Construct command */
cmd = sdscatfmt(cmd, "*%i\r\n", argc);
for (j=0; j < argc; j++) {
len = argvlen ? argvlen[j] : strlen(argv[j]);
- cmd = sdscatfmt(cmd, "$%T\r\n", len);
+ cmd = sdscatfmt(cmd, "$%U\r\n", len);
cmd = sdscatlen(cmd, argv[j], len);
cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1);
}
@@ -527,11 +639,11 @@ void redisFreeSdsCommand(sds cmd) {
* lengths. If the latter is set to NULL, strlen will be used to compute the
* argument lengths.
*/
-int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) {
+long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) {
char *cmd = NULL; /* final command */
- int pos; /* position in final command */
- size_t len;
- int totlen, j;
+ size_t pos; /* position in final command */
+ size_t len, totlen;
+ int j;
/* Abort on a NULL target */
if (target == NULL)
@@ -545,7 +657,7 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz
}
/* Build the command at protocol level */
- cmd = malloc(totlen+1);
+ cmd = hi_malloc(totlen+1);
if (cmd == NULL)
return -1;
@@ -566,7 +678,7 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz
}
void redisFreeCommand(char *cmd) {
- free(cmd);
+ hi_free(cmd);
}
void __redisSetError(redisContext *c, int type, const char *str) {
@@ -581,7 +693,7 @@ void __redisSetError(redisContext *c, int type, const char *str) {
} else {
/* Only REDIS_ERR_IO may lack a description! */
assert(type == REDIS_ERR_IO);
- __redis_strerror_r(errno, c->errstr, sizeof(c->errstr));
+ strerror_r(errno, c->errstr, sizeof(c->errstr));
}
}
@@ -589,21 +701,23 @@ redisReader *redisReaderCreate(void) {
return redisReaderCreateWithFunctions(&defaultFunctions);
}
+static void redisPushAutoFree(void *privdata, void *reply) {
+ (void)privdata;
+ freeReplyObject(reply);
+}
+
static redisContext *redisContextInit(void) {
redisContext *c;
- c = calloc(1,sizeof(redisContext));
+ c = hi_calloc(1, sizeof(*c));
if (c == NULL)
return NULL;
- c->err = 0;
- c->errstr[0] = '\0';
+ c->funcs = &redisContextDefaultFuncs;
+
c->obuf = sdsempty();
c->reader = redisReaderCreate();
- c->tcp.host = NULL;
- c->tcp.source_addr = NULL;
- c->unix_sock.path = NULL;
- c->timeout = NULL;
+ c->fd = REDIS_INVALID_FD;
if (c->obuf == NULL || c->reader == NULL) {
redisFree(c);
@@ -616,26 +730,33 @@ static redisContext *redisContextInit(void) {
void redisFree(redisContext *c) {
if (c == NULL)
return;
- if (c->fd > 0)
- close(c->fd);
- if (c->obuf != NULL)
- sdsfree(c->obuf);
- if (c->reader != NULL)
- redisReaderFree(c->reader);
- if (c->tcp.host)
- free(c->tcp.host);
- if (c->tcp.source_addr)
- free(c->tcp.source_addr);
- if (c->unix_sock.path)
- free(c->unix_sock.path);
- if (c->timeout)
- free(c->timeout);
- free(c);
+
+ if (c->funcs && c->funcs->close) {
+ c->funcs->close(c);
+ }
+
+ sdsfree(c->obuf);
+ redisReaderFree(c->reader);
+ hi_free(c->tcp.host);
+ hi_free(c->tcp.source_addr);
+ hi_free(c->unix_sock.path);
+ hi_free(c->connect_timeout);
+ hi_free(c->command_timeout);
+ hi_free(c->saddr);
+
+ if (c->privdata && c->free_privdata)
+ c->free_privdata(c->privdata);
+
+ if (c->funcs && c->funcs->free_privctx)
+ c->funcs->free_privctx(c->privctx);
+
+ memset(c, 0xff, sizeof(*c));
+ hi_free(c);
}
-int redisFreeKeepFd(redisContext *c) {
- int fd = c->fd;
- c->fd = -1;
+redisFD redisFreeKeepFd(redisContext *c) {
+ redisFD fd = c->fd;
+ c->fd = REDIS_INVALID_FD;
redisFree(c);
return fd;
}
@@ -644,8 +765,13 @@ int redisReconnect(redisContext *c) {
c->err = 0;
memset(c->errstr, '\0', strlen(c->errstr));
- if (c->fd > 0) {
- close(c->fd);
+ if (c->privctx && c->funcs->free_privctx) {
+ c->funcs->free_privctx(c->privctx);
+ c->privctx = NULL;
+ }
+
+ if (c->funcs && c->funcs->close) {
+ c->funcs->close(c);
}
sdsfree(c->obuf);
@@ -654,126 +780,161 @@ int redisReconnect(redisContext *c) {
c->obuf = sdsempty();
c->reader = redisReaderCreate();
+ if (c->obuf == NULL || c->reader == NULL) {
+ __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
+ return REDIS_ERR;
+ }
+
+ int ret = REDIS_ERR;
if (c->connection_type == REDIS_CONN_TCP) {
- return redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port,
- c->timeout, c->tcp.source_addr);
+ ret = redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port,
+ c->connect_timeout, c->tcp.source_addr);
} else if (c->connection_type == REDIS_CONN_UNIX) {
- return redisContextConnectUnix(c, c->unix_sock.path, c->timeout);
+ ret = redisContextConnectUnix(c, c->unix_sock.path, c->connect_timeout);
} else {
/* Something bad happened here and shouldn't have. There isn't
enough information in the context to reconnect. */
__redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect");
+ ret = REDIS_ERR;
}
- return REDIS_ERR;
-}
+ if (c->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
+ redisContextSetTimeout(c, *c->command_timeout);
+ }
-/* Connect to a Redis instance. On error the field error in the returned
- * context will be set to the return value of the error function.
- * When no set of reply functions is given, the default set will be used. */
-redisContext *redisConnect(const char *ip, int port) {
- redisContext *c;
+ return ret;
+}
- c = redisContextInit();
- if (c == NULL)
+redisContext *redisConnectWithOptions(const redisOptions *options) {
+ redisContext *c = redisContextInit();
+ if (c == NULL) {
return NULL;
+ }
+ if (!(options->options & REDIS_OPT_NONBLOCK)) {
+ c->flags |= REDIS_BLOCK;
+ }
+ if (options->options & REDIS_OPT_REUSEADDR) {
+ c->flags |= REDIS_REUSEADDR;
+ }
+ if (options->options & REDIS_OPT_NOAUTOFREE) {
+ c->flags |= REDIS_NO_AUTO_FREE;
+ }
+ if (options->options & REDIS_OPT_NOAUTOFREEREPLIES) {
+ c->flags |= REDIS_NO_AUTO_FREE_REPLIES;
+ }
+ if (options->options & REDIS_OPT_PREFER_IPV4) {
+ c->flags |= REDIS_PREFER_IPV4;
+ }
+ if (options->options & REDIS_OPT_PREFER_IPV6) {
+ c->flags |= REDIS_PREFER_IPV6;
+ }
- c->flags |= REDIS_BLOCK;
- redisContextConnectTcp(c,ip,port,NULL);
- return c;
-}
+ /* Set any user supplied RESP3 PUSH handler or use freeReplyObject
+ * as a default unless specifically flagged that we don't want one. */
+ if (options->push_cb != NULL)
+ redisSetPushCallback(c, options->push_cb);
+ else if (!(options->options & REDIS_OPT_NO_PUSH_AUTOFREE))
+ redisSetPushCallback(c, redisPushAutoFree);
-redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
- redisContext *c;
+ c->privdata = options->privdata;
+ c->free_privdata = options->free_privdata;
- c = redisContextInit();
- if (c == NULL)
+ if (redisContextUpdateConnectTimeout(c, options->connect_timeout) != REDIS_OK ||
+ redisContextUpdateCommandTimeout(c, options->command_timeout) != REDIS_OK) {
+ __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
+ return c;
+ }
+
+ if (options->type == REDIS_CONN_TCP) {
+ redisContextConnectBindTcp(c, options->endpoint.tcp.ip,
+ options->endpoint.tcp.port, options->connect_timeout,
+ options->endpoint.tcp.source_addr);
+ } else if (options->type == REDIS_CONN_UNIX) {
+ redisContextConnectUnix(c, options->endpoint.unix_socket,
+ options->connect_timeout);
+ } else if (options->type == REDIS_CONN_USERFD) {
+ c->fd = options->endpoint.fd;
+ c->flags |= REDIS_CONNECTED;
+ } else {
+ redisFree(c);
return NULL;
+ }
+
+ if (c->err == 0 && c->fd != REDIS_INVALID_FD &&
+ options->command_timeout != NULL && (c->flags & REDIS_BLOCK))
+ {
+ redisContextSetTimeout(c, *options->command_timeout);
+ }
- c->flags |= REDIS_BLOCK;
- redisContextConnectTcp(c,ip,port,&tv);
return c;
}
-redisContext *redisConnectNonBlock(const char *ip, int port) {
- redisContext *c;
+/* Connect to a Redis instance. On error the field error in the returned
+ * context will be set to the return value of the error function.
+ * When no set of reply functions is given, the default set will be used. */
+redisContext *redisConnect(const char *ip, int port) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ return redisConnectWithOptions(&options);
+}
- c = redisContextInit();
- if (c == NULL)
- return NULL;
+redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.connect_timeout = &tv;
+ return redisConnectWithOptions(&options);
+}
- c->flags &= ~REDIS_BLOCK;
- redisContextConnectTcp(c,ip,port,NULL);
- return c;
+redisContext *redisConnectNonBlock(const char *ip, int port) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.options |= REDIS_OPT_NONBLOCK;
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectBindNonBlock(const char *ip, int port,
const char *source_addr) {
- redisContext *c = redisContextInit();
- if (c == NULL)
- return NULL;
- c->flags &= ~REDIS_BLOCK;
- redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.endpoint.tcp.source_addr = source_addr;
+ options.options |= REDIS_OPT_NONBLOCK;
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
const char *source_addr) {
- redisContext *c = redisContextInit();
- if (c == NULL)
- return NULL;
- c->flags &= ~REDIS_BLOCK;
- c->flags |= REDIS_REUSEADDR;
- redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.endpoint.tcp.source_addr = source_addr;
+ options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR;
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectUnix(const char *path) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->flags |= REDIS_BLOCK;
- redisContextConnectUnix(c,path,NULL);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->flags |= REDIS_BLOCK;
- redisContextConnectUnix(c,path,&tv);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ options.connect_timeout = &tv;
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectUnixNonBlock(const char *path) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->flags &= ~REDIS_BLOCK;
- redisContextConnectUnix(c,path,NULL);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ options.options |= REDIS_OPT_NONBLOCK;
+ return redisConnectWithOptions(&options);
}
-redisContext *redisConnectFd(int fd) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->fd = fd;
- c->flags |= REDIS_BLOCK | REDIS_CONNECTED;
- return c;
+redisContext *redisConnectFd(redisFD fd) {
+ redisOptions options = {0};
+ options.type = REDIS_CONN_USERFD;
+ options.endpoint.fd = fd;
+ return redisConnectWithOptions(&options);
}
/* Set read/write timeout on a blocking socket. */
@@ -783,17 +944,31 @@ int redisSetTimeout(redisContext *c, const struct timeval tv) {
return REDIS_ERR;
}
+int redisEnableKeepAliveWithInterval(redisContext *c, int interval) {
+ return redisKeepAlive(c, interval);
+}
+
/* Enable connection KeepAlive. */
int redisEnableKeepAlive(redisContext *c) {
- if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK)
- return REDIS_ERR;
- return REDIS_OK;
+ return redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL);
+}
+
+/* Set the socket option TCP_USER_TIMEOUT. */
+int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout) {
+ return redisContextSetTcpUserTimeout(c, timeout);
+}
+
+/* Set a user provided RESP3 PUSH handler and return any old one set. */
+redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn) {
+ redisPushFn *old = c->push_cb;
+ c->push_cb = fn;
+ return old;
}
/* Use this function to handle a read event on the descriptor. It will try
* and read some bytes from the socket and feed them to the reply parser.
*
- * After this function is called, you may use redisContextReadReply to
+ * After this function is called, you may use redisGetReplyFromReader to
* see if there is a reply available. */
int redisBufferRead(redisContext *c) {
char buf[1024*16];
@@ -803,22 +978,13 @@ int redisBufferRead(redisContext *c) {
if (c->err)
return REDIS_ERR;
- nread = read(c->fd,buf,sizeof(buf));
- if (nread == -1) {
- if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
- /* Try again later */
- } else {
- __redisSetError(c,REDIS_ERR_IO,NULL);
- return REDIS_ERR;
- }
- } else if (nread == 0) {
- __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
+ nread = c->funcs->read(c, buf, sizeof(buf));
+ if (nread < 0) {
+ return REDIS_ERR;
+ }
+ if (nread > 0 && redisReaderFeed(c->reader, buf, nread) != REDIS_OK) {
+ __redisSetError(c, c->reader->err, c->reader->errstr);
return REDIS_ERR;
- } else {
- if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) {
- __redisSetError(c,c->reader->err,c->reader->errstr);
- return REDIS_ERR;
- }
}
return REDIS_OK;
}
@@ -829,45 +995,68 @@ int redisBufferRead(redisContext *c) {
* successfully written to the socket. When the buffer is empty after the
* write operation, "done" is set to 1 (if given).
*
- * Returns REDIS_ERR if an error occurred trying to write and sets
- * c->errstr to hold the appropriate error string.
+ * Returns REDIS_ERR if an unrecoverable error occurred in the underlying
+ * c->funcs->write function.
*/
int redisBufferWrite(redisContext *c, int *done) {
- int nwritten;
/* Return early when the context has seen an error. */
if (c->err)
return REDIS_ERR;
if (sdslen(c->obuf) > 0) {
- nwritten = write(c->fd,c->obuf,sdslen(c->obuf));
- if (nwritten == -1) {
- if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
- /* Try again later */
- } else {
- __redisSetError(c,REDIS_ERR_IO,NULL);
- return REDIS_ERR;
- }
+ ssize_t nwritten = c->funcs->write(c);
+ if (nwritten < 0) {
+ return REDIS_ERR;
} else if (nwritten > 0) {
- if (nwritten == (signed)sdslen(c->obuf)) {
+ if (nwritten == (ssize_t)sdslen(c->obuf)) {
sdsfree(c->obuf);
c->obuf = sdsempty();
+ if (c->obuf == NULL)
+ goto oom;
} else {
- sdsrange(c->obuf,nwritten,-1);
+ if (sdsrange(c->obuf,nwritten,-1) < 0) goto oom;
}
}
}
if (done != NULL) *done = (sdslen(c->obuf) == 0);
return REDIS_OK;
+
+oom:
+ __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
+ return REDIS_ERR;
+}
+
+/* Internal helper that returns 1 if the reply was a RESP3 PUSH
+ * message and we handled it with a user-provided callback. */
+static int redisHandledPushReply(redisContext *c, void *reply) {
+ if (reply && c->push_cb && redisIsPushReply(reply)) {
+ c->push_cb(c->privdata, reply);
+ return 1;
+ }
+
+ return 0;
}
-/* Internal helper function to try and get a reply from the reader,
- * or set an error in the context otherwise. */
+/* Get a reply from our reader or set an error in the context. */
int redisGetReplyFromReader(redisContext *c, void **reply) {
- if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) {
+ if (redisReaderGetReply(c->reader, reply) == REDIS_ERR) {
__redisSetError(c,c->reader->err,c->reader->errstr);
return REDIS_ERR;
}
+
+ return REDIS_OK;
+}
+
+/* Internal helper to get the next reply from our reader while handling
+ * any PUSH messages we encounter along the way. This is separate from
+ * redisGetReplyFromReader so as to not change its behavior. */
+static int redisNextInBandReplyFromReader(redisContext *c, void **reply) {
+ do {
+ if (redisGetReplyFromReader(c, reply) == REDIS_ERR)
+ return REDIS_ERR;
+ } while (redisHandledPushReply(c, *reply));
+
return REDIS_OK;
}
@@ -876,7 +1065,7 @@ int redisGetReply(redisContext *c, void **reply) {
void *aux = NULL;
/* Try to read pending replies */
- if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
+ if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR)
return REDIS_ERR;
/* For the blocking context, flush output buffer and read reply */
@@ -891,13 +1080,19 @@ int redisGetReply(redisContext *c, void **reply) {
do {
if (redisBufferRead(c) == REDIS_ERR)
return REDIS_ERR;
- if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
+
+ if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR)
return REDIS_ERR;
} while (aux == NULL);
}
- /* Set reply object */
- if (reply != NULL) *reply = aux;
+ /* Set reply or free it if we were passed NULL */
+ if (reply != NULL) {
+ *reply = aux;
+ } else {
+ freeReplyObject(aux);
+ }
+
return REDIS_OK;
}
@@ -944,11 +1139,11 @@ int redisvAppendCommand(redisContext *c, const char *format, va_list ap) {
}
if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
- free(cmd);
+ hi_free(cmd);
return REDIS_ERR;
}
- free(cmd);
+ hi_free(cmd);
return REDIS_OK;
}
@@ -964,7 +1159,7 @@ int redisAppendCommand(redisContext *c, const char *format, ...) {
int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
sds cmd;
- int len;
+ long long len;
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
if (len == -1) {
@@ -1011,9 +1206,8 @@ void *redisvCommand(redisContext *c, const char *format, va_list ap) {
void *redisCommand(redisContext *c, const char *format, ...) {
va_list ap;
- void *reply = NULL;
va_start(ap,format);
- reply = redisvCommand(c,format,ap);
+ void *reply = redisvCommand(c,format,ap);
va_end(ap);
return reply;
}
diff --git a/contrib/hiredis/hiredis.h b/contrib/hiredis/hiredis.h
index 6b531b910..14af8dace 100644
--- a/contrib/hiredis/hiredis.h
+++ b/contrib/hiredis/hiredis.h
@@ -35,15 +35,20 @@
#define __HIREDIS_H
#include "read.h"
#include <stdarg.h> /* for va_list */
+#ifndef _MSC_VER
#include <sys/time.h> /* for struct timeval */
+#else
+struct timeval; /* forward declaration */
+typedef long long ssize_t;
+#endif
#include <stdint.h> /* uintXX_t, etc */
-#include <string.h> /* strerror_r, etc */
#include "sds.h" /* for sds */
+#include "alloc.h" /* for allocation wrappers */
-#define HIREDIS_MAJOR 0
-#define HIREDIS_MINOR 13
-#define HIREDIS_PATCH 3
-#define HIREDIS_SONAME 0.13
+#define HIREDIS_MAJOR 1
+#define HIREDIS_MINOR 2
+#define HIREDIS_PATCH 0
+#define HIREDIS_SONAME 1.1.0
/* Connection type can be blocking or non-blocking and is set in the
* least significant bit of the flags field in redisContext. */
@@ -75,35 +80,37 @@
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */
#define REDIS_REUSEADDR 0x80
+/* Flag that is set when the async connection supports push replies. */
+#define REDIS_SUPPORTS_PUSH 0x100
+
+/**
+ * Flag that indicates the user does not want the context to
+ * be automatically freed upon error
+ */
+#define REDIS_NO_AUTO_FREE 0x200
+
+/* Flag that indicates the user does not want replies to be automatically freed */
+#define REDIS_NO_AUTO_FREE_REPLIES 0x400
+
+/* Flags to prefer IPv6 or IPv4 when doing DNS lookup. (If both are set,
+ * AF_UNSPEC is used.) */
+#define REDIS_PREFER_IPV4 0x800
+#define REDIS_PREFER_IPV6 0x1000
+
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
/* number of times we retry to connect in the case of EADDRNOTAVAIL and
* SO_REUSEADDR is being used. */
#define REDIS_CONNECT_RETRIES 10
-/* strerror_r has two completely different prototypes and behaviors
- * depending on system issues, so we need to operate on the error buffer
- * differently depending on which strerror_r we're using. */
-#ifndef _GNU_SOURCE
-/* "regular" POSIX strerror_r that does the right thing. */
-#define __redis_strerror_r(errno, buf, len) \
- do { \
- strerror_r((errno), (buf), (len)); \
- } while (0)
-#else
-/* "bad" GNU strerror_r we need to clean up after. */
-#define __redis_strerror_r(errno, buf, len) \
- do { \
- char *err_str = strerror((errno)); \
- /* If return value _isn't_ the start of the buffer we passed in, \
- * then GNU strerror_r returned an internal static buffer and we \
- * need to copy the result into our private buffer. */ \
- if (err_str != (buf)) { \
- buf[(len)-1] = '\0'; \
- strncat((buf), err_str, ((len) - 1)); \
- } \
- } while (0)
-#endif
+/* Forward declarations for structs defined elsewhere */
+struct redisAsyncContext;
+struct redisContext;
+
+/* RESP3 push helpers and callback prototypes */
+#define redisIsPushReply(r) (((redisReply*)(r))->type == REDIS_REPLY_PUSH)
+typedef void (redisPushFn)(void *, void *);
+typedef void (redisAsyncPushFn)(struct redisAsyncContext *, void *);
#ifdef __cplusplus
extern "C" {
@@ -113,8 +120,13 @@ extern "C" {
typedef struct redisReply {
int type; /* REDIS_REPLY_* */
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
- int len; /* Length of string */
- char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
+ double dval; /* The double when type is REDIS_REPLY_DOUBLE */
+ size_t len; /* Length of string */
+ char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
+ REDIS_REPLY_VERB, REDIS_REPLY_DOUBLE (in additional to dval),
+ and REDIS_REPLY_BIGNUM. */
+ char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null
+ terminated 3 character content type, such as "txt". */
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
} redisReply;
@@ -125,35 +137,136 @@ redisReader *redisReaderCreate(void);
void freeReplyObject(void *reply);
/* Functions to format a command according to the protocol. */
-#ifdef __GNUC__
-__attribute__((format(printf, 2, 0)))
-#endif
int redisvFormatCommand(char **target, const char *format, va_list ap);
-#ifdef __GNUC__
-__attribute__((format(printf, 2, 3)))
-#endif
int redisFormatCommand(char **target, const char *format, ...);
-int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
-int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen);
+long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
+long long redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen);
void redisFreeCommand(char *cmd);
void redisFreeSdsCommand(sds cmd);
enum redisConnectionType {
REDIS_CONN_TCP,
REDIS_CONN_UNIX,
+ REDIS_CONN_USERFD
};
+struct redisSsl;
+
+#define REDIS_OPT_NONBLOCK 0x01
+#define REDIS_OPT_REUSEADDR 0x02
+#define REDIS_OPT_NOAUTOFREE 0x04 /* Don't automatically free the async
+ * object on a connection failure, or
+ * other implicit conditions. Only free
+ * on an explicit call to disconnect()
+ * or free() */
+#define REDIS_OPT_NO_PUSH_AUTOFREE 0x08 /* Don't automatically intercept and
+ * free RESP3 PUSH replies. */
+#define REDIS_OPT_NOAUTOFREEREPLIES 0x10 /* Don't automatically free replies. */
+#define REDIS_OPT_PREFER_IPV4 0x20 /* Prefer IPv4 in DNS lookups. */
+#define REDIS_OPT_PREFER_IPV6 0x40 /* Prefer IPv6 in DNS lookups. */
+#define REDIS_OPT_PREFER_IP_UNSPEC (REDIS_OPT_PREFER_IPV4 | REDIS_OPT_PREFER_IPV6)
+
+/* In Unix systems a file descriptor is a regular signed int, with -1
+ * representing an invalid descriptor. In Windows it is a SOCKET
+ * (32- or 64-bit unsigned integer depending on the architecture), where
+ * all bits set (~0) is INVALID_SOCKET. */
+#ifndef _WIN32
+typedef int redisFD;
+#define REDIS_INVALID_FD -1
+#else
+#ifdef _WIN64
+typedef unsigned long long redisFD; /* SOCKET = 64-bit UINT_PTR */
+#else
+typedef unsigned long redisFD; /* SOCKET = 32-bit UINT_PTR */
+#endif
+#define REDIS_INVALID_FD ((redisFD)(~0)) /* INVALID_SOCKET */
+#endif
+
+typedef struct {
+ /*
+ * the type of connection to use. This also indicates which
+ * `endpoint` member field to use
+ */
+ int type;
+ /* bit field of REDIS_OPT_xxx */
+ int options;
+ /* timeout value for connect operation. If NULL, no timeout is used */
+ const struct timeval *connect_timeout;
+ /* timeout value for commands. If NULL, no timeout is used. This can be
+ * updated at runtime with redisSetTimeout/redisAsyncSetTimeout. */
+ const struct timeval *command_timeout;
+ union {
+ /** use this field for tcp/ip connections */
+ struct {
+ const char *source_addr;
+ const char *ip;
+ int port;
+ } tcp;
+ /** use this field for unix domain sockets */
+ const char *unix_socket;
+ /**
+ * use this field to have hiredis operate an already-open
+ * file descriptor */
+ redisFD fd;
+ } endpoint;
+
+ /* Optional user defined data/destructor */
+ void *privdata;
+ void (*free_privdata)(void *);
+
+ /* A user defined PUSH message callback */
+ redisPushFn *push_cb;
+ redisAsyncPushFn *async_push_cb;
+} redisOptions;
+
+/**
+ * Helper macros to initialize options to their specified fields.
+ */
+#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) do { \
+ (opts)->type = REDIS_CONN_TCP; \
+ (opts)->endpoint.tcp.ip = ip_; \
+ (opts)->endpoint.tcp.port = port_; \
+ } while(0)
+
+#define REDIS_OPTIONS_SET_UNIX(opts, path) do { \
+ (opts)->type = REDIS_CONN_UNIX; \
+ (opts)->endpoint.unix_socket = path; \
+ } while(0)
+
+#define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) do { \
+ (opts)->privdata = data; \
+ (opts)->free_privdata = dtor; \
+ } while(0)
+
+typedef struct redisContextFuncs {
+ void (*close)(struct redisContext *);
+ void (*free_privctx)(void *);
+ void (*async_read)(struct redisAsyncContext *);
+ void (*async_write)(struct redisAsyncContext *);
+
+ /* Read/Write data to the underlying communication stream, returning the
+ * number of bytes read/written. In the event of an unrecoverable error
+ * these functions shall return a value < 0. In the event of a
+ * recoverable error, they should return 0. */
+ ssize_t (*read)(struct redisContext *, char *, size_t);
+ ssize_t (*write)(struct redisContext *);
+} redisContextFuncs;
+
+
/* Context for a connection to Redis */
typedef struct redisContext {
+ const redisContextFuncs *funcs; /* Function table */
+
int err; /* Error flags, 0 when there is no error */
char errstr[128]; /* String representation of error when applicable */
- int fd;
+ redisFD fd;
int flags;
char *obuf; /* Write buffer */
redisReader *reader; /* Protocol reader */
enum redisConnectionType connection_type;
- struct timeval *timeout;
+ struct timeval *connect_timeout;
+ struct timeval *command_timeout;
struct {
char *host;
@@ -165,8 +278,24 @@ typedef struct redisContext {
char *path;
} unix_sock;
+ /* For non-blocking connect */
+ struct sockaddr *saddr;
+ size_t addrlen;
+
+ /* Optional data and corresponding destructor users can use to provide
+ * context to a given redisContext. Not used by hiredis. */
+ void *privdata;
+ void (*free_privdata)(void *);
+
+ /* Internal context pointer presently used by hiredis to manage
+ * SSL connections. */
+ void *privctx;
+
+ /* An optional RESP3 PUSH handler */
+ redisPushFn *push_cb;
} redisContext;
+redisContext *redisConnectWithOptions(const redisOptions *options);
redisContext *redisConnect(const char *ip, int port);
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
redisContext *redisConnectNonBlock(const char *ip, int port);
@@ -177,7 +306,7 @@ redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
redisContext *redisConnectUnix(const char *path);
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
redisContext *redisConnectUnixNonBlock(const char *path);
-redisContext *redisConnectFd(int fd);
+redisContext *redisConnectFd(redisFD fd);
/**
* Reconnect the given context using the saved information.
@@ -190,10 +319,13 @@ redisContext *redisConnectFd(int fd);
*/
int redisReconnect(redisContext *c);
+redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn);
int redisSetTimeout(redisContext *c, const struct timeval tv);
int redisEnableKeepAlive(redisContext *c);
+int redisEnableKeepAliveWithInterval(redisContext *c, int interval);
+int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout);
void redisFree(redisContext *c);
-int redisFreeKeepFd(redisContext *c);
+redisFD redisFreeKeepFd(redisContext *c);
int redisBufferRead(redisContext *c);
int redisBufferWrite(redisContext *c, int *done);
@@ -210,13 +342,7 @@ int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len);
/* Write a command to the output buffer. Use these functions in blocking mode
* to get a pipeline of commands. */
-#ifdef __GNUC__
-__attribute__((format(printf, 2, 0)))
-#endif
int redisvAppendCommand(redisContext *c, const char *format, va_list ap);
-#ifdef __GNUC__
-__attribute__((format(printf, 2, 3)))
-#endif
int redisAppendCommand(redisContext *c, const char *format, ...);
int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
@@ -225,13 +351,7 @@ int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const s
* NULL if there was an error in performing the request, otherwise it will
* return the reply. In a non-blocking context, it is identical to calling
* only redisAppendCommand and will always return NULL. */
-#ifdef __GNUC__
-__attribute__((format(printf, 2, 0)))
-#endif
void *redisvCommand(redisContext *c, const char *format, va_list ap);
-#ifdef __GNUC__
-__attribute__((format(printf, 2, 3)))
-#endif
void *redisCommand(redisContext *c, const char *format, ...);
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
diff --git a/contrib/hiredis/hiredis_ssl.h b/contrib/hiredis/hiredis_ssl.h
new file mode 100644
index 000000000..5f92cca9b
--- /dev/null
+++ b/contrib/hiredis/hiredis_ssl.h
@@ -0,0 +1,163 @@
+
+/*
+ * Copyright (c) 2019, Redis Labs
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_SSL_H
+#define __HIREDIS_SSL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This is the underlying struct for SSL in ssl.h, which is not included to
+ * keep build dependencies short here.
+ */
+struct ssl_st;
+
+/* A wrapper around OpenSSL SSL_CTX to allow easy SSL use without directly
+ * calling OpenSSL.
+ */
+typedef struct redisSSLContext redisSSLContext;
+
+/**
+ * Initialization errors that redisCreateSSLContext() may return.
+ */
+
+typedef enum {
+ REDIS_SSL_CTX_NONE = 0, /* No Error */
+ REDIS_SSL_CTX_CREATE_FAILED, /* Failed to create OpenSSL SSL_CTX */
+ REDIS_SSL_CTX_CERT_KEY_REQUIRED, /* Client cert and key must both be specified or skipped */
+ REDIS_SSL_CTX_CA_CERT_LOAD_FAILED, /* Failed to load CA Certificate or CA Path */
+ REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED, /* Failed to load client certificate */
+ REDIS_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED, /* Failed to set client default certificate directory */
+ REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED, /* Failed to load private key */
+ REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED, /* Failed to open system certificate store */
+ REDIS_SSL_CTX_OS_CERT_ADD_FAILED /* Failed to add CA certificates obtained from system to the SSL context */
+} redisSSLContextError;
+
+/* Constants that mirror OpenSSL's verify modes. By default,
+ * REDIS_SSL_VERIFY_PEER is used with redisCreateSSLContext().
+ * Some Redis clients disable peer verification if there are no
+ * certificates specified.
+ */
+#define REDIS_SSL_VERIFY_NONE 0x00
+#define REDIS_SSL_VERIFY_PEER 0x01
+#define REDIS_SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02
+#define REDIS_SSL_VERIFY_CLIENT_ONCE 0x04
+#define REDIS_SSL_VERIFY_POST_HANDSHAKE 0x08
+
+/* Options to create an OpenSSL context. */
+typedef struct {
+ const char *cacert_filename;
+ const char *capath;
+ const char *cert_filename;
+ const char *private_key_filename;
+ const char *server_name;
+ int verify_mode;
+} redisSSLOptions;
+
+/**
+ * Return the error message corresponding with the specified error code.
+ */
+
+const char *redisSSLContextGetError(redisSSLContextError error);
+
+/**
+ * Helper function to initialize the OpenSSL library.
+ *
+ * OpenSSL requires one-time initialization before it can be used. Callers should
+ * call this function only once, and only if OpenSSL is not directly initialized
+ * elsewhere.
+ */
+int redisInitOpenSSL(void);
+
+/**
+ * Helper function to initialize an OpenSSL context that can be used
+ * to initiate SSL connections.
+ *
+ * cacert_filename is an optional name of a CA certificate/bundle file to load
+ * and use for validation.
+ *
+ * capath is an optional directory path where trusted CA certificate files are
+ * stored in an OpenSSL-compatible structure.
+ *
+ * cert_filename and private_key_filename are optional names of a client side
+ * certificate and private key files to use for authentication. They need to
+ * be both specified or omitted.
+ *
+ * server_name is an optional and will be used as a server name indication
+ * (SNI) TLS extension.
+ *
+ * If error is non-null, it will be populated in case the context creation fails
+ * (returning a NULL).
+ */
+
+redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
+ const char *cert_filename, const char *private_key_filename,
+ const char *server_name, redisSSLContextError *error);
+
+/**
+ * Helper function to initialize an OpenSSL context that can be used
+ * to initiate SSL connections. This is a more extensible version of redisCreateSSLContext().
+ *
+ * options contains a structure of SSL options to use.
+ *
+ * If error is non-null, it will be populated in case the context creation fails
+ * (returning a NULL).
+*/
+redisSSLContext *redisCreateSSLContextWithOptions(redisSSLOptions *options,
+ redisSSLContextError *error);
+
+/**
+ * Free a previously created OpenSSL context.
+ */
+void redisFreeSSLContext(redisSSLContext *redis_ssl_ctx);
+
+/**
+ * Initiate SSL on an existing redisContext.
+ *
+ * This is similar to redisInitiateSSL() but does not require the caller
+ * to directly interact with OpenSSL, and instead uses a redisSSLContext
+ * previously created using redisCreateSSLContext().
+ */
+
+int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx);
+
+/**
+ * Initiate SSL/TLS negotiation on a provided OpenSSL SSL object.
+ */
+
+int redisInitiateSSL(redisContext *c, struct ssl_st *ssl);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __HIREDIS_SSL_H */
diff --git a/contrib/hiredis/net.c b/contrib/hiredis/net.c
index 97fd42c23..1e016384f 100644
--- a/contrib/hiredis/net.c
+++ b/contrib/hiredis/net.c
@@ -34,45 +34,78 @@
#include "fmacros.h"
#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/select.h>
-#include <sys/un.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <arpa/inet.h>
-#include <unistd.h>
#include <fcntl.h>
#include <string.h>
-#include <netdb.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
-#include <poll.h>
#include <limits.h>
#include <stdlib.h>
#include "net.h"
#include "sds.h"
+#include "sockcompat.h"
+#include "win32.h"
/* Defined in hiredis.c */
void __redisSetError(redisContext *c, int type, const char *str);
-static void redisContextCloseFd(redisContext *c) {
- if (c && c->fd >= 0) {
+int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout);
+
+void redisNetClose(redisContext *c) {
+ if (c && c->fd != REDIS_INVALID_FD) {
close(c->fd);
- c->fd = -1;
+ c->fd = REDIS_INVALID_FD;
+ }
+}
+
+ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) {
+ ssize_t nread = recv(c->fd, buf, bufcap, 0);
+ if (nread == -1) {
+ if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
+ /* Try again later */
+ return 0;
+ } else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) {
+ /* especially in windows */
+ __redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout");
+ return -1;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, strerror(errno));
+ return -1;
+ }
+ } else if (nread == 0) {
+ __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
+ return -1;
+ } else {
+ return nread;
}
}
+ssize_t redisNetWrite(redisContext *c) {
+ ssize_t nwritten;
+
+ nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0);
+ if (nwritten < 0) {
+ if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
+ /* Try again */
+ return 0;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, strerror(errno));
+ return -1;
+ }
+ }
+
+ return nwritten;
+}
+
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
+ int errorno = errno; /* snprintf() may change errno */
char buf[128] = { 0 };
- char *p;
size_t len = 0;
if (prefix != NULL)
len = snprintf(buf,sizeof(buf),"%s: ",prefix);
- p = buf + len;
- __redis_strerror_r(errno, p, sizeof(buf) - len);
+ strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len);
__redisSetError(c,type,buf);
}
@@ -80,15 +113,15 @@ static int redisSetReuseAddr(redisContext *c) {
int on = 1;
if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
return REDIS_OK;
}
static int redisCreateSocket(redisContext *c, int type) {
- int s;
- if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
+ redisFD s;
+ if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
return REDIS_ERR;
}
@@ -102,6 +135,7 @@ static int redisCreateSocket(redisContext *c, int type) {
}
static int redisSetBlocking(redisContext *c, int blocking) {
+#ifndef _WIN32
int flags;
/* Set the socket nonblocking.
@@ -109,7 +143,7 @@ static int redisSetBlocking(redisContext *c, int blocking) {
* interrupted by a signal. */
if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
@@ -120,16 +154,25 @@ static int redisSetBlocking(redisContext *c, int blocking) {
if (fcntl(c->fd, F_SETFL, flags) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
+#else
+ u_long mode = blocking ? 0 : 1;
+ if (ioctl(c->fd, FIONBIO, &mode) == -1) {
+ __redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)");
+ redisNetClose(c);
+ return REDIS_ERR;
+ }
+#endif /* _WIN32 */
return REDIS_OK;
}
int redisKeepAlive(redisContext *c, int interval) {
int val = 1;
- int fd = c->fd;
+ redisFD fd = c->fd;
+#ifndef _WIN32
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
@@ -137,14 +180,13 @@ int redisKeepAlive(redisContext *c, int interval) {
val = interval;
-#ifdef _OSX
+#if defined(__APPLE__) && defined(__MACH__)
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
}
#else
#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)
- val = interval;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
@@ -164,35 +206,57 @@ int redisKeepAlive(redisContext *c, int interval) {
}
#endif
#endif
+#else
+ int res;
+ res = win32_redisKeepAlive(fd, interval * 1000);
+ if (res != 0) {
+ __redisSetError(c, REDIS_ERR_OTHER, strerror(res));
+ return REDIS_ERR;
+ }
+#endif
return REDIS_OK;
}
-static int redisSetTcpNoDelay(redisContext *c) {
+int redisSetTcpNoDelay(redisContext *c) {
int yes = 1;
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
return REDIS_OK;
}
-#define __MAX_MSEC (((LONG_MAX) - 999) / 1000)
+int redisContextSetTcpUserTimeout(redisContext *c, unsigned int timeout) {
+ int res;
+#ifdef TCP_USER_TIMEOUT
+ res = setsockopt(c->fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout));
+#else
+ res = -1;
+ errno = ENOTSUP;
+ (void)timeout;
+#endif
+ if (res == -1) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_USER_TIMEOUT)");
+ redisNetClose(c);
+ return REDIS_ERR;
+ }
+ return REDIS_OK;
+}
-static int redisContextWaitReady(redisContext *c, const struct timeval *timeout) {
- struct pollfd wfd[1];
- long msec;
+#define __MAX_MSEC (((LONG_MAX) - 999) / 1000)
- msec = -1;
- wfd[0].fd = c->fd;
- wfd[0].events = POLLOUT;
+static int redisContextTimeoutMsec(redisContext *c, long *result)
+{
+ const struct timeval *timeout = c->connect_timeout;
+ long msec = -1;
/* Only use timeout when not NULL. */
if (timeout != NULL) {
if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
- __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL);
- redisContextCloseFd(c);
+ __redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified");
+ *result = msec;
return REDIS_ERR;
}
@@ -203,33 +267,81 @@ static int redisContextWaitReady(redisContext *c, const struct timeval *timeout)
}
}
+ *result = msec;
+ return REDIS_OK;
+}
+
+static int redisContextWaitReady(redisContext *c, long msec) {
+ struct pollfd wfd[1];
+
+ wfd[0].fd = c->fd;
+ wfd[0].events = POLLOUT;
+
if (errno == EINPROGRESS) {
int res;
if ((res = poll(wfd, 1, msec)) == -1) {
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
} else if (res == 0) {
errno = ETIMEDOUT;
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
- if (redisCheckSocketError(c) != REDIS_OK)
+ if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) {
+ redisCheckSocketError(c);
return REDIS_ERR;
+ }
return REDIS_OK;
}
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
+int redisCheckConnectDone(redisContext *c, int *completed) {
+ int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen);
+ if (rc == 0) {
+ *completed = 1;
+ return REDIS_OK;
+ }
+ int error = errno;
+ if (error == EINPROGRESS) {
+ /* must check error to see if connect failed. Get the socket error */
+ int fail, so_error;
+ socklen_t optlen = sizeof(so_error);
+ fail = getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &so_error, &optlen);
+ if (fail == 0) {
+ if (so_error == 0) {
+ /* Socket is connected! */
+ *completed = 1;
+ return REDIS_OK;
+ }
+ /* connection error; */
+ errno = so_error;
+ error = so_error;
+ }
+ }
+ switch (error) {
+ case EISCONN:
+ *completed = 1;
+ return REDIS_OK;
+ case EALREADY:
+ case EWOULDBLOCK:
+ *completed = 0;
+ return REDIS_OK;
+ default:
+ return REDIS_ERR;
+ }
+}
+
int redisCheckSocketError(redisContext *c) {
- int err = 0;
+ int err = 0, errno_saved = errno;
socklen_t errlen = sizeof(err);
if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
@@ -237,6 +349,10 @@ int redisCheckSocketError(redisContext *c) {
return REDIS_ERR;
}
+ if (err == 0) {
+ err = errno_saved;
+ }
+
if (err) {
errno = err;
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
@@ -247,27 +363,69 @@ int redisCheckSocketError(redisContext *c) {
}
int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
- if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) {
+ const void *to_ptr = &tv;
+ size_t to_sz = sizeof(tv);
+
+ if (redisContextUpdateCommandTimeout(c, &tv) != REDIS_OK) {
+ __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
+ return REDIS_ERR;
+ }
+ if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
return REDIS_ERR;
}
- if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) {
+ if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
return REDIS_ERR;
}
return REDIS_OK;
}
+int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout) {
+ /* Same timeval struct, short circuit */
+ if (c->connect_timeout == timeout)
+ return REDIS_OK;
+
+ /* Allocate context timeval if we need to */
+ if (c->connect_timeout == NULL) {
+ c->connect_timeout = hi_malloc(sizeof(*c->connect_timeout));
+ if (c->connect_timeout == NULL)
+ return REDIS_ERR;
+ }
+
+ memcpy(c->connect_timeout, timeout, sizeof(*c->connect_timeout));
+ return REDIS_OK;
+}
+
+int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout) {
+ /* Same timeval struct, short circuit */
+ if (c->command_timeout == timeout)
+ return REDIS_OK;
+
+ /* Allocate context timeval if we need to */
+ if (c->command_timeout == NULL) {
+ c->command_timeout = hi_malloc(sizeof(*c->command_timeout));
+ if (c->command_timeout == NULL)
+ return REDIS_ERR;
+ }
+
+ memcpy(c->command_timeout, timeout, sizeof(*c->command_timeout));
+ return REDIS_OK;
+}
+
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
const struct timeval *timeout,
const char *source_addr) {
- int s, rv, n;
+ redisFD s;
+ int rv, n;
char _port[6]; /* strlen("65535"); */
struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
int blocking = (c->flags & REDIS_BLOCK);
int reuseaddr = (c->flags & REDIS_REUSEADDR);
int reuses = 0;
+ long timeout_msec = -1;
+ servinfo = NULL;
c->connection_type = REDIS_CONN_TCP;
c->tcp.port = port;
@@ -279,57 +437,61 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
* This is a bit ugly, but atleast it works and doesn't leak memory.
**/
if (c->tcp.host != addr) {
- if (c->tcp.host)
- free(c->tcp.host);
+ hi_free(c->tcp.host);
- c->tcp.host = strdup(addr);
+ c->tcp.host = hi_strdup(addr);
+ if (c->tcp.host == NULL)
+ goto oom;
}
if (timeout) {
- if (c->timeout != timeout) {
- if (c->timeout == NULL)
- c->timeout = malloc(sizeof(struct timeval));
-
- memcpy(c->timeout, timeout, sizeof(struct timeval));
- }
+ if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)
+ goto oom;
} else {
- if (c->timeout)
- free(c->timeout);
- c->timeout = NULL;
+ hi_free(c->connect_timeout);
+ c->connect_timeout = NULL;
+ }
+
+ if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {
+ goto error;
}
if (source_addr == NULL) {
- free(c->tcp.source_addr);
+ hi_free(c->tcp.source_addr);
c->tcp.source_addr = NULL;
} else if (c->tcp.source_addr != source_addr) {
- free(c->tcp.source_addr);
- c->tcp.source_addr = strdup(source_addr);
+ hi_free(c->tcp.source_addr);
+ c->tcp.source_addr = hi_strdup(source_addr);
}
snprintf(_port, 6, "%d", port);
memset(&hints,0,sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
- if (!blocking) {
- /* Rspamd specific: never try to resolve on non-blocking conn requests */
- hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
- }
-
- /* Try with IPv6 if no IPv4 address was found. We do it in this order since
- * in a Redis client you can't afford to test if you have IPv6 connectivity
- * as this would add latency to every connect. Otherwise a more sensible
- * route could be: Use IPv6 if both addresses are available and there is IPv6
- * connectivity. */
- if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) {
- hints.ai_family = AF_INET6;
- if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
- __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
- return REDIS_ERR;
- }
+
+ /* DNS lookup. To use dual stack, set both flags to prefer both IPv4 and
+ * IPv6. By default, for historical reasons, we try IPv4 first and then we
+ * try IPv6 only if no IPv4 address was found. */
+ if (c->flags & REDIS_PREFER_IPV6 && c->flags & REDIS_PREFER_IPV4)
+ hints.ai_family = AF_UNSPEC;
+ else if (c->flags & REDIS_PREFER_IPV6)
+ hints.ai_family = AF_INET6;
+ else
+ hints.ai_family = AF_INET;
+
+ rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo);
+ if (rv != 0 && hints.ai_family != AF_UNSPEC) {
+ /* Try again with the other IP version. */
+ hints.ai_family = (hints.ai_family == AF_INET) ? AF_INET6 : AF_INET;
+ rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo);
+ }
+ if (rv != 0) {
+ __redisSetError(c, REDIS_ERR_OTHER, gai_strerror(rv));
+ return REDIS_ERR;
}
for (p = servinfo; p != NULL; p = p->ai_next) {
addrretry:
- if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
+ if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD)
continue;
c->fd = s;
@@ -349,6 +511,7 @@ addrretry:
n = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
sizeof(n)) < 0) {
+ freeaddrinfo(bservinfo);
goto error;
}
}
@@ -367,32 +530,50 @@ addrretry:
goto error;
}
}
- if (redisSetTcpNoDelay(c) != REDIS_OK)
- goto error;
+
+ /* For repeat connection */
+ hi_free(c->saddr);
+ c->saddr = hi_malloc(p->ai_addrlen);
+ if (c->saddr == NULL)
+ goto oom;
+
+ memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
+ c->addrlen = p->ai_addrlen;
+
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
if (errno == EHOSTUNREACH) {
- redisContextCloseFd(c);
+ redisNetClose(c);
continue;
- } else if (errno == EINPROGRESS && !blocking) {
- /* This is ok. */
+ } else if (errno == EINPROGRESS) {
+ if (blocking) {
+ goto wait_for_ready;
+ }
+ /* This is ok.
+ * Note that even when it's in blocking mode, we unset blocking
+ * for `connect()`
+ */
} else if (errno == EADDRNOTAVAIL && reuseaddr) {
if (++reuses >= REDIS_CONNECT_RETRIES) {
goto error;
} else {
+ redisNetClose(c);
goto addrretry;
}
} else {
- if (redisContextWaitReady(c,c->timeout) != REDIS_OK)
+ wait_for_ready:
+ if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
+ goto error;
+ if (redisSetTcpNoDelay(c) != REDIS_OK)
goto error;
}
}
+ if (blocking && redisSetBlocking(c,1) != REDIS_OK)
+ goto error;
c->flags |= REDIS_CONNECTED;
rv = REDIS_OK;
goto end;
}
- if (blocking && redisSetBlocking(c,1) != REDIS_OK)
- goto error;
if (p == NULL) {
char buf[128];
snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno));
@@ -400,10 +581,15 @@ addrretry:
goto error;
}
+oom:
+ __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
error:
rv = REDIS_ERR;
end:
- freeaddrinfo(servinfo);
+ if(servinfo) {
+ freeaddrinfo(servinfo);
+ }
+
return rv; // Need to return REDIS_OK if alright
}
@@ -419,38 +605,51 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
}
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
+#ifndef _WIN32
int blocking = (c->flags & REDIS_BLOCK);
- struct sockaddr_un sa;
+ struct sockaddr_un *sa;
+ long timeout_msec = -1;
- if (redisCreateSocket(c,AF_LOCAL) < 0)
+ if (redisCreateSocket(c,AF_UNIX) < 0)
return REDIS_ERR;
if (redisSetBlocking(c,0) != REDIS_OK)
return REDIS_ERR;
c->connection_type = REDIS_CONN_UNIX;
- if (c->unix_sock.path != path)
- c->unix_sock.path = strdup(path);
+ if (c->unix_sock.path != path) {
+ hi_free(c->unix_sock.path);
- if (timeout) {
- if (c->timeout != timeout) {
- if (c->timeout == NULL)
- c->timeout = malloc(sizeof(struct timeval));
+ c->unix_sock.path = hi_strdup(path);
+ if (c->unix_sock.path == NULL)
+ goto oom;
+ }
- memcpy(c->timeout, timeout, sizeof(struct timeval));
- }
+ if (timeout) {
+ if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)
+ goto oom;
} else {
- if (c->timeout)
- free(c->timeout);
- c->timeout = NULL;
+ hi_free(c->connect_timeout);
+ c->connect_timeout = NULL;
}
- sa.sun_family = AF_LOCAL;
- strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
- if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
+ if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
+ return REDIS_ERR;
+
+ /* Don't leak sockaddr if we're reconnecting */
+ if (c->saddr) hi_free(c->saddr);
+
+ sa = (struct sockaddr_un*)(c->saddr = hi_malloc(sizeof(struct sockaddr_un)));
+ if (sa == NULL)
+ goto oom;
+
+ c->addrlen = sizeof(struct sockaddr_un);
+ sa->sun_family = AF_UNIX;
+ strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1);
+ if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) {
if (errno == EINPROGRESS && !blocking) {
/* This is ok. */
} else {
- if (redisContextWaitReady(c,c->timeout) != REDIS_OK)
+ if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
return REDIS_ERR;
}
}
@@ -461,4 +660,13 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
c->flags |= REDIS_CONNECTED;
return REDIS_OK;
+#else
+ /* We currently do not support Unix sockets for Windows. */
+ /* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */
+ errno = EPROTONOSUPPORT;
+ return REDIS_ERR;
+#endif /* _WIN32 */
+oom:
+ __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
+ return REDIS_ERR;
}
diff --git a/contrib/hiredis/net.h b/contrib/hiredis/net.h
index 2f1a0bf85..e15d46264 100644
--- a/contrib/hiredis/net.h
+++ b/contrib/hiredis/net.h
@@ -37,9 +37,9 @@
#include "hiredis.h"
-#if defined(__sun)
-#define AF_LOCAL AF_UNIX
-#endif
+void redisNetClose(redisContext *c);
+ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap);
+ssize_t redisNetWrite(redisContext *c);
int redisCheckSocketError(redisContext *c);
int redisContextSetTimeout(redisContext *c, const struct timeval tv);
@@ -49,5 +49,9 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
const char *source_addr);
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout);
int redisKeepAlive(redisContext *c, int interval);
+int redisCheckConnectDone(redisContext *c, int *completed);
+
+int redisSetTcpNoDelay(redisContext *c);
+int redisContextSetTcpUserTimeout(redisContext *c, unsigned int timeout);
#endif
diff --git a/contrib/hiredis/read.c b/contrib/hiredis/read.c
index df1a467a9..9c8f86906 100644
--- a/contrib/hiredis/read.c
+++ b/contrib/hiredis/read.c
@@ -29,19 +29,26 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
-
#include "fmacros.h"
#include <string.h>
#include <stdlib.h>
#ifndef _MSC_VER
#include <unistd.h>
+#include <strings.h>
#endif
#include <assert.h>
#include <errno.h>
#include <ctype.h>
+#include <limits.h>
+#include <math.h>
+#include "alloc.h"
#include "read.h"
#include "sds.h"
+#include "win32.h"
+
+/* Initial size of our nested reply stack and how much we grow it when needd */
+#define REDIS_READER_STACK_SIZE 9
static void __redisReaderSetError(redisReader *r, int type, const char *str) {
size_t len;
@@ -52,11 +59,9 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) {
}
/* Clear input buffer on errors. */
- if (r->buf != NULL) {
- sdsfree(r->buf);
- r->buf = NULL;
- r->pos = r->len = 0;
- }
+ sdsfree(r->buf);
+ r->buf = NULL;
+ r->pos = r->len = 0;
/* Reset task stack. */
r->ridx = -1;
@@ -118,58 +123,103 @@ static char *readBytes(redisReader *r, unsigned int bytes) {
/* Find pointer to \r\n. */
static char *seekNewline(char *s, size_t len) {
- int pos = 0;
- int _len = len-1;
-
- /* Position should be < len-1 because the character at "pos" should be
- * followed by a \n. Note that strchr cannot be used because it doesn't
- * allow to search a limited length and the buffer that is being searched
- * might not have a trailing NULL character. */
- while (pos < _len) {
- while(pos < _len && s[pos] != '\r') pos++;
- if (s[pos] != '\r') {
- /* Not found. */
- return NULL;
- } else {
- if (s[pos+1] == '\n') {
- /* Found. */
- return s+pos;
- } else {
- /* Continue searching. */
- pos++;
- }
+ char *ret;
+
+ /* We cannot match with fewer than 2 bytes */
+ if (len < 2)
+ return NULL;
+
+ /* Search up to len - 1 characters */
+ len--;
+
+ /* Look for the \r */
+ while ((ret = memchr(s, '\r', len)) != NULL) {
+ if (ret[1] == '\n') {
+ /* Found. */
+ break;
}
+ /* Continue searching. */
+ ret++;
+ len -= ret - s;
+ s = ret;
}
- return NULL;
+
+ return ret;
}
-/* Read a long long value starting at *s, under the assumption that it will be
- * terminated by \r\n. Ambiguously returns -1 for unexpected input. */
-static long long readLongLong(char *s) {
- long long v = 0;
- int dec, mult = 1;
- char c;
-
- if (*s == '-') {
- mult = -1;
- s++;
- } else if (*s == '+') {
- mult = 1;
- s++;
+/* Convert a string into a long long. Returns REDIS_OK if the string could be
+ * parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value
+ * will be set to the parsed value when appropriate.
+ *
+ * Note that this function demands that the string strictly represents
+ * a long long: no spaces or other characters before or after the string
+ * representing the number are accepted, nor zeroes at the start if not
+ * for the string "0" representing the zero number.
+ *
+ * Because of its strictness, it is safe to use this function to check if
+ * you can convert a string into a long long, and obtain back the string
+ * from the number without any loss in the string representation. */
+static int string2ll(const char *s, size_t slen, long long *value) {
+ const char *p = s;
+ size_t plen = 0;
+ int negative = 0;
+ unsigned long long v;
+
+ if (plen == slen)
+ return REDIS_ERR;
+
+ /* Special case: first and only digit is 0. */
+ if (slen == 1 && p[0] == '0') {
+ if (value != NULL) *value = 0;
+ return REDIS_OK;
}
- while ((c = *(s++)) != '\r') {
- dec = c - '0';
- if (dec >= 0 && dec < 10) {
- v *= 10;
- v += dec;
- } else {
- /* Should not happen... */
- return -1;
- }
+ if (p[0] == '-') {
+ negative = 1;
+ p++; plen++;
+
+ /* Abort on only a negative sign. */
+ if (plen == slen)
+ return REDIS_ERR;
+ }
+
+ /* First digit should be 1-9, otherwise the string should just be 0. */
+ if (p[0] >= '1' && p[0] <= '9') {
+ v = p[0]-'0';
+ p++; plen++;
+ } else if (p[0] == '0' && slen == 1) {
+ *value = 0;
+ return REDIS_OK;
+ } else {
+ return REDIS_ERR;
+ }
+
+ while (plen < slen && p[0] >= '0' && p[0] <= '9') {
+ if (v > (ULLONG_MAX / 10)) /* Overflow. */
+ return REDIS_ERR;
+ v *= 10;
+
+ if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */
+ return REDIS_ERR;
+ v += p[0]-'0';
+
+ p++; plen++;
}
- return mult*v;
+ /* Return if not all bytes were used. */
+ if (plen < slen)
+ return REDIS_ERR;
+
+ if (negative) {
+ if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
+ return REDIS_ERR;
+ if (value != NULL) *value = -v;
+ } else {
+ if (v > LLONG_MAX) /* Overflow. */
+ return REDIS_ERR;
+ if (value != NULL) *value = v;
+ }
+ return REDIS_OK;
}
static char *readLine(redisReader *r, int *_len) {
@@ -196,9 +246,12 @@ static void moveToNextTask(redisReader *r) {
return;
}
- cur = &(r->rstack[r->ridx]);
- prv = &(r->rstack[r->ridx-1]);
- assert(prv->type == REDIS_REPLY_ARRAY);
+ cur = r->task[r->ridx];
+ prv = r->task[r->ridx-1];
+ assert(prv->type == REDIS_REPLY_ARRAY ||
+ prv->type == REDIS_REPLY_MAP ||
+ prv->type == REDIS_REPLY_SET ||
+ prv->type == REDIS_REPLY_PUSH);
if (cur->idx == prv->elements-1) {
r->ridx--;
} else {
@@ -213,23 +266,118 @@ static void moveToNextTask(redisReader *r) {
}
static int processLineItem(redisReader *r) {
- redisReadTask *cur = &(r->rstack[r->ridx]);
+ redisReadTask *cur = r->task[r->ridx];
void *obj;
char *p;
int len;
if ((p = readLine(r,&len)) != NULL) {
if (cur->type == REDIS_REPLY_INTEGER) {
- if (r->fn && r->fn->createInteger)
- obj = r->fn->createInteger(cur,readLongLong(p));
- else
+ long long v;
+
+ if (string2ll(p, len, &v) == REDIS_ERR) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bad integer value");
+ return REDIS_ERR;
+ }
+
+ if (r->fn && r->fn->createInteger) {
+ obj = r->fn->createInteger(cur,v);
+ } else {
obj = (void*)REDIS_REPLY_INTEGER;
+ }
+ } else if (cur->type == REDIS_REPLY_DOUBLE) {
+ char buf[326], *eptr;
+ double d;
+
+ if ((size_t)len >= sizeof(buf)) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Double value is too large");
+ return REDIS_ERR;
+ }
+
+ memcpy(buf,p,len);
+ buf[len] = '\0';
+
+ if (len == 3 && strcasecmp(buf,"inf") == 0) {
+ d = INFINITY; /* Positive infinite. */
+ } else if (len == 4 && strcasecmp(buf,"-inf") == 0) {
+ d = -INFINITY; /* Negative infinite. */
+ } else if ((len == 3 && strcasecmp(buf,"nan") == 0) ||
+ (len == 4 && strcasecmp(buf, "-nan") == 0)) {
+ d = NAN; /* nan. */
+ } else {
+ d = strtod((char*)buf,&eptr);
+ /* RESP3 only allows "inf", "-inf", and finite values, while
+ * strtod() allows other variations on infinity,
+ * etc. We explicity handle our two allowed infinite cases and NaN
+ * above, so strtod() should only result in finite values. */
+ if (buf[0] == '\0' || eptr != &buf[len] || !isfinite(d)) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bad double value");
+ return REDIS_ERR;
+ }
+ }
+
+ if (r->fn && r->fn->createDouble) {
+ obj = r->fn->createDouble(cur,d,buf,len);
+ } else {
+ obj = (void*)REDIS_REPLY_DOUBLE;
+ }
+ } else if (cur->type == REDIS_REPLY_NIL) {
+ if (len != 0) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bad nil value");
+ return REDIS_ERR;
+ }
+
+ if (r->fn && r->fn->createNil)
+ obj = r->fn->createNil(cur);
+ else
+ obj = (void*)REDIS_REPLY_NIL;
+ } else if (cur->type == REDIS_REPLY_BOOL) {
+ int bval;
+
+ if (len != 1 || !strchr("tTfF", p[0])) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bad bool value");
+ return REDIS_ERR;
+ }
+
+ bval = p[0] == 't' || p[0] == 'T';
+ if (r->fn && r->fn->createBool)
+ obj = r->fn->createBool(cur,bval);
+ else
+ obj = (void*)REDIS_REPLY_BOOL;
+ } else if (cur->type == REDIS_REPLY_BIGNUM) {
+ /* Ensure all characters are decimal digits (with possible leading
+ * minus sign). */
+ for (int i = 0; i < len; i++) {
+ /* XXX Consider: Allow leading '+'? Error on leading '0's? */
+ if (i == 0 && p[0] == '-') continue;
+ if (p[i] < '0' || p[i] > '9') {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bad bignum value");
+ return REDIS_ERR;
+ }
+ }
+ if (r->fn && r->fn->createString)
+ obj = r->fn->createString(cur,p,len);
+ else
+ obj = (void*)REDIS_REPLY_BIGNUM;
} else {
/* Type will be error or status. */
+ for (int i = 0; i < len; i++) {
+ if (p[i] == '\r' || p[i] == '\n') {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bad simple string value");
+ return REDIS_ERR;
+ }
+ }
if (r->fn && r->fn->createString)
obj = r->fn->createString(cur,p,len);
else
- obj = (void*)(size_t)(cur->type);
+ obj = (void*)(uintptr_t)(cur->type);
}
if (obj == NULL) {
@@ -247,10 +395,10 @@ static int processLineItem(redisReader *r) {
}
static int processBulkItem(redisReader *r) {
- redisReadTask *cur = &(r->rstack[r->ridx]);
+ redisReadTask *cur = r->task[r->ridx];
void *obj = NULL;
char *p, *s;
- long len;
+ long long len;
unsigned long bytelen;
int success = 0;
@@ -259,9 +407,20 @@ static int processBulkItem(redisReader *r) {
if (s != NULL) {
p = r->buf+r->pos;
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
- len = readLongLong(p);
- if (len < 0) {
+ if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bad bulk string length");
+ return REDIS_ERR;
+ }
+
+ if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bulk string length out of range");
+ return REDIS_ERR;
+ }
+
+ if (len == -1) {
/* The nil object can always be created. */
if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur);
@@ -272,10 +431,18 @@ static int processBulkItem(redisReader *r) {
/* Only continue when the buffer contains the entire bulk item. */
bytelen += len+2; /* include \r\n */
if (r->pos+bytelen <= r->len) {
+ if ((cur->type == REDIS_REPLY_VERB && len < 4) ||
+ (cur->type == REDIS_REPLY_VERB && s[5] != ':'))
+ {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Verbatim string 4 bytes of content type are "
+ "missing or incorrectly encoded.");
+ return REDIS_ERR;
+ }
if (r->fn && r->fn->createString)
obj = r->fn->createString(cur,s+2,len);
else
- obj = (void*)REDIS_REPLY_STRING;
+ obj = (void*)(uintptr_t)cur->type;
success = 1;
}
}
@@ -299,24 +466,61 @@ static int processBulkItem(redisReader *r) {
return REDIS_ERR;
}
-static int processMultiBulkItem(redisReader *r) {
- redisReadTask *cur = &(r->rstack[r->ridx]);
+static int redisReaderGrow(redisReader *r) {
+ redisReadTask **aux;
+ int newlen;
+
+ /* Grow our stack size */
+ newlen = r->tasks + REDIS_READER_STACK_SIZE;
+ aux = hi_realloc(r->task, sizeof(*r->task) * newlen);
+ if (aux == NULL)
+ goto oom;
+
+ r->task = aux;
+
+ /* Allocate new tasks */
+ for (; r->tasks < newlen; r->tasks++) {
+ r->task[r->tasks] = hi_calloc(1, sizeof(**r->task));
+ if (r->task[r->tasks] == NULL)
+ goto oom;
+ }
+
+ return REDIS_OK;
+oom:
+ __redisReaderSetErrorOOM(r);
+ return REDIS_ERR;
+}
+
+/* Process the array, map and set types. */
+static int processAggregateItem(redisReader *r) {
+ redisReadTask *cur = r->task[r->ridx];
void *obj;
char *p;
- long elements;
- int root = 0;
+ long long elements;
+ int root = 0, len;
- /* Set error for nested multi bulks with depth > 7 */
- if (r->ridx == 8) {
- __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
- "No support for nested multi bulk replies with depth > 7");
- return REDIS_ERR;
+ if (r->ridx == r->tasks - 1) {
+ if (redisReaderGrow(r) == REDIS_ERR)
+ return REDIS_ERR;
}
- if ((p = readLine(r,NULL)) != NULL) {
- elements = readLongLong(p);
+ if ((p = readLine(r,&len)) != NULL) {
+ if (string2ll(p, len, &elements) == REDIS_ERR) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bad multi-bulk length");
+ return REDIS_ERR;
+ }
+
root = (r->ridx == 0);
+ if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX) ||
+ (r->maxelements > 0 && elements > r->maxelements))
+ {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Multi-bulk length out of range");
+ return REDIS_ERR;
+ }
+
if (elements == -1) {
if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur);
@@ -330,10 +534,12 @@ static int processMultiBulkItem(redisReader *r) {
moveToNextTask(r);
} else {
+ if (cur->type == REDIS_REPLY_MAP) elements *= 2;
+
if (r->fn && r->fn->createArray)
obj = r->fn->createArray(cur,elements);
else
- obj = (void*)REDIS_REPLY_ARRAY;
+ obj = (void*)(uintptr_t)cur->type;
if (obj == NULL) {
__redisReaderSetErrorOOM(r);
@@ -345,12 +551,12 @@ static int processMultiBulkItem(redisReader *r) {
cur->elements = elements;
cur->obj = obj;
r->ridx++;
- r->rstack[r->ridx].type = -1;
- r->rstack[r->ridx].elements = -1;
- r->rstack[r->ridx].idx = 0;
- r->rstack[r->ridx].obj = NULL;
- r->rstack[r->ridx].parent = cur;
- r->rstack[r->ridx].privdata = r->privdata;
+ r->task[r->ridx]->type = -1;
+ r->task[r->ridx]->elements = -1;
+ r->task[r->ridx]->idx = 0;
+ r->task[r->ridx]->obj = NULL;
+ r->task[r->ridx]->parent = cur;
+ r->task[r->ridx]->privdata = r->privdata;
} else {
moveToNextTask(r);
}
@@ -365,7 +571,7 @@ static int processMultiBulkItem(redisReader *r) {
}
static int processItem(redisReader *r) {
- redisReadTask *cur = &(r->rstack[r->ridx]);
+ redisReadTask *cur = r->task[r->ridx];
char *p;
/* check if we need to read type */
@@ -381,12 +587,36 @@ static int processItem(redisReader *r) {
case ':':
cur->type = REDIS_REPLY_INTEGER;
break;
+ case ',':
+ cur->type = REDIS_REPLY_DOUBLE;
+ break;
+ case '_':
+ cur->type = REDIS_REPLY_NIL;
+ break;
case '$':
cur->type = REDIS_REPLY_STRING;
break;
case '*':
cur->type = REDIS_REPLY_ARRAY;
break;
+ case '%':
+ cur->type = REDIS_REPLY_MAP;
+ break;
+ case '~':
+ cur->type = REDIS_REPLY_SET;
+ break;
+ case '#':
+ cur->type = REDIS_REPLY_BOOL;
+ break;
+ case '=':
+ cur->type = REDIS_REPLY_VERB;
+ break;
+ case '>':
+ cur->type = REDIS_REPLY_PUSH;
+ break;
+ case '(':
+ cur->type = REDIS_REPLY_BIGNUM;
+ break;
default:
__redisReaderSetErrorProtocolByte(r,*p);
return REDIS_ERR;
@@ -402,11 +632,19 @@ static int processItem(redisReader *r) {
case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS:
case REDIS_REPLY_INTEGER:
+ case REDIS_REPLY_DOUBLE:
+ case REDIS_REPLY_NIL:
+ case REDIS_REPLY_BOOL:
+ case REDIS_REPLY_BIGNUM:
return processLineItem(r);
case REDIS_REPLY_STRING:
+ case REDIS_REPLY_VERB:
return processBulkItem(r);
case REDIS_REPLY_ARRAY:
- return processMultiBulkItem(r);
+ case REDIS_REPLY_MAP:
+ case REDIS_REPLY_SET:
+ case REDIS_REPLY_PUSH:
+ return processAggregateItem(r);
default:
assert(NULL);
return REDIS_ERR; /* Avoid warning. */
@@ -416,30 +654,53 @@ static int processItem(redisReader *r) {
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
redisReader *r;
- r = calloc(sizeof(redisReader),1);
+ r = hi_calloc(1,sizeof(redisReader));
if (r == NULL)
return NULL;
- r->err = 0;
- r->errstr[0] = '\0';
- r->fn = fn;
r->buf = sdsempty();
- r->maxbuf = REDIS_READER_MAX_BUF;
- if (r->buf == NULL) {
- free(r);
- return NULL;
+ if (r->buf == NULL)
+ goto oom;
+
+ r->task = hi_calloc(REDIS_READER_STACK_SIZE, sizeof(*r->task));
+ if (r->task == NULL)
+ goto oom;
+
+ for (; r->tasks < REDIS_READER_STACK_SIZE; r->tasks++) {
+ r->task[r->tasks] = hi_calloc(1, sizeof(**r->task));
+ if (r->task[r->tasks] == NULL)
+ goto oom;
}
+ r->fn = fn;
+ r->maxbuf = REDIS_READER_MAX_BUF;
+ r->maxelements = REDIS_READER_MAX_ARRAY_ELEMENTS;
r->ridx = -1;
+
return r;
+oom:
+ redisReaderFree(r);
+ return NULL;
}
void redisReaderFree(redisReader *r) {
+ if (r == NULL)
+ return;
+
if (r->reply != NULL && r->fn && r->fn->freeObject)
r->fn->freeObject(r->reply);
- if (r->buf != NULL)
- sdsfree(r->buf);
- free(r);
+
+ if (r->task) {
+ /* We know r->task[i] is allocated if i < r->tasks */
+ for (int i = 0; i < r->tasks; i++) {
+ hi_free(r->task[i]);
+ }
+
+ hi_free(r->task);
+ }
+
+ sdsfree(r->buf);
+ hi_free(r);
}
int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
@@ -455,23 +716,22 @@ int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) {
sdsfree(r->buf);
r->buf = sdsempty();
- r->pos = 0;
+ if (r->buf == 0) goto oom;
- /* r->buf should not be NULL since we just free'd a larger one. */
- assert(r->buf != NULL);
+ r->pos = 0;
}
newbuf = sdscatlen(r->buf,buf,len);
- if (newbuf == NULL) {
- __redisReaderSetErrorOOM(r);
- return REDIS_ERR;
- }
+ if (newbuf == NULL) goto oom;
r->buf = newbuf;
r->len = sdslen(r->buf);
}
return REDIS_OK;
+oom:
+ __redisReaderSetErrorOOM(r);
+ return REDIS_ERR;
}
int redisReaderGetReply(redisReader *r, void **reply) {
@@ -489,12 +749,12 @@ int redisReaderGetReply(redisReader *r, void **reply) {
/* Set first item to process when the stack is empty. */
if (r->ridx == -1) {
- r->rstack[0].type = -1;
- r->rstack[0].elements = -1;
- r->rstack[0].idx = -1;
- r->rstack[0].obj = NULL;
- r->rstack[0].parent = NULL;
- r->rstack[0].privdata = r->privdata;
+ r->task[0]->type = -1;
+ r->task[0]->elements = -1;
+ r->task[0]->idx = -1;
+ r->task[0]->obj = NULL;
+ r->task[0]->parent = NULL;
+ r->task[0]->privdata = r->privdata;
r->ridx = 0;
}
@@ -510,15 +770,18 @@ int redisReaderGetReply(redisReader *r, void **reply) {
/* Discard part of the buffer when we've consumed at least 1k, to avoid
* doing unnecessary calls to memmove() in sds.c. */
if (r->pos >= 1024) {
- sdsrange(r->buf,r->pos,-1);
+ if (sdsrange(r->buf,r->pos,-1) < 0) return REDIS_ERR;
r->pos = 0;
r->len = sdslen(r->buf);
}
/* Emit a reply when there is one. */
if (r->ridx == -1) {
- if (reply != NULL)
+ if (reply != NULL) {
*reply = r->reply;
+ } else if (r->reply != NULL && r->fn && r->fn->freeObject) {
+ r->fn->freeObject(r->reply);
+ }
r->reply = NULL;
}
return REDIS_OK;
diff --git a/contrib/hiredis/read.h b/contrib/hiredis/read.h
index 180e6c6bf..2d74d77a5 100644
--- a/contrib/hiredis/read.h
+++ b/contrib/hiredis/read.h
@@ -45,6 +45,7 @@
#define REDIS_ERR_EOF 3 /* End of file */
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
#define REDIS_ERR_OOM 5 /* Out of memory */
+#define REDIS_ERR_TIMEOUT 6 /* Timed out */
#define REDIS_ERR_OTHER 2 /* Everything else... */
#define REDIS_REPLY_STRING 1
@@ -53,8 +54,20 @@
#define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6
+#define REDIS_REPLY_DOUBLE 7
+#define REDIS_REPLY_BOOL 8
+#define REDIS_REPLY_MAP 9
+#define REDIS_REPLY_SET 10
+#define REDIS_REPLY_ATTR 11
+#define REDIS_REPLY_PUSH 12
+#define REDIS_REPLY_BIGNUM 13
+#define REDIS_REPLY_VERB 14
-#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
+/* Default max unused reader buffer. */
+#define REDIS_READER_MAX_BUF (1024*16)
+
+/* Default multi-bulk element limit */
+#define REDIS_READER_MAX_ARRAY_ELEMENTS ((1LL<<32) - 1)
#ifdef __cplusplus
extern "C" {
@@ -62,7 +75,7 @@ extern "C" {
typedef struct redisReadTask {
int type;
- int elements; /* number of elements in multibulk container */
+ long long elements; /* number of elements in multibulk container */
int idx; /* index in parent (array) object */
void *obj; /* holds user-generated value for a read task */
struct redisReadTask *parent; /* parent task */
@@ -71,9 +84,11 @@ typedef struct redisReadTask {
typedef struct redisReplyObjectFunctions {
void *(*createString)(const redisReadTask*, char*, size_t);
- void *(*createArray)(const redisReadTask*, int);
+ void *(*createArray)(const redisReadTask*, size_t);
void *(*createInteger)(const redisReadTask*, long long);
+ void *(*createDouble)(const redisReadTask*, double, char*, size_t);
void *(*createNil)(const redisReadTask*);
+ void *(*createBool)(const redisReadTask*, int);
void (*freeObject)(void*);
} redisReplyObjectFunctions;
@@ -85,8 +100,11 @@ typedef struct redisReader {
size_t pos; /* Buffer cursor */
size_t len; /* Buffer length */
size_t maxbuf; /* Max length of unused buffer */
+ long long maxelements; /* Max multi-bulk elements */
+
+ redisReadTask **task;
+ int tasks;
- redisReadTask rstack[9];
int ridx; /* Index of current read task */
void *reply; /* Temporary reply pointer */
@@ -100,14 +118,9 @@ void redisReaderFree(redisReader *r);
int redisReaderFeed(redisReader *r, const char *buf, size_t len);
int redisReaderGetReply(redisReader *r, void **reply);
-/* Backwards compatibility, can be removed on big version bump. */
-#define redisReplyReaderCreate redisReaderCreate
-#define redisReplyReaderFree redisReaderFree
-#define redisReplyReaderFeed redisReaderFeed
-#define redisReplyReaderGetReply redisReaderGetReply
-#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
-#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply)
-#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr)
+#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
+#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply)
+#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr)
#ifdef __cplusplus
}
diff --git a/contrib/hiredis/sds.c b/contrib/hiredis/sds.c
index 5e7551647..21ecec04e 100644
--- a/contrib/hiredis/sds.c
+++ b/contrib/hiredis/sds.c
@@ -1,6 +1,8 @@
-/* SDS (Simple Dynamic Strings), A C dynamic strings library.
+/* SDSLib 2.0 -- A C dynamic strings library
*
- * Copyright (c) 2006-2014, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2015, Oran Agra
+ * Copyright (c) 2015, Redis Labs, Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -28,41 +30,111 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
+#include "fmacros.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
-
+#include <limits.h>
#include "sds.h"
+#include "sdsalloc.h"
+
+static inline int sdsHdrSize(char type) {
+ switch(type&SDS_TYPE_MASK) {
+ case SDS_TYPE_5:
+ return sizeof(struct sdshdr5);
+ case SDS_TYPE_8:
+ return sizeof(struct sdshdr8);
+ case SDS_TYPE_16:
+ return sizeof(struct sdshdr16);
+ case SDS_TYPE_32:
+ return sizeof(struct sdshdr32);
+ case SDS_TYPE_64:
+ return sizeof(struct sdshdr64);
+ }
+ return 0;
+}
+
+static inline char sdsReqType(size_t string_size) {
+ if (string_size < 32)
+ return SDS_TYPE_5;
+ if (string_size < 0xff)
+ return SDS_TYPE_8;
+ if (string_size < 0xffff)
+ return SDS_TYPE_16;
+ if (string_size < 0xffffffff)
+ return SDS_TYPE_32;
+ return SDS_TYPE_64;
+}
/* Create a new sds string with the content specified by the 'init' pointer
* and 'initlen'.
* If NULL is used for 'init' the string is initialized with zero bytes.
*
- * The string is always null-termined (all the sds strings are, always) so
+ * The string is always null-terminated (all the sds strings are, always) so
* even if you create an sds string with:
*
- * mystring = sdsnewlen("abc",3");
+ * mystring = sdsnewlen("abc",3);
*
* You can print the string with printf() as there is an implicit \0 at the
* end of the string. However the string is binary safe and can contain
* \0 characters in the middle, as the length is stored in the sds header. */
sds sdsnewlen(const void *init, size_t initlen) {
- struct sdshdr *sh;
-
- if (init) {
- sh = malloc(sizeof *sh+initlen+1);
- } else {
- sh = calloc(sizeof *sh+initlen+1,1);
- }
+ void *sh;
+ sds s;
+ char type = sdsReqType(initlen);
+ /* Empty strings are usually created in order to append. Use type 8
+ * since type 5 is not good at this. */
+ if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
+ int hdrlen = sdsHdrSize(type);
+ unsigned char *fp; /* flags pointer. */
+
+ if (hdrlen+initlen+1 <= initlen) return NULL; /* Catch size_t overflow */
+ sh = s_malloc(hdrlen+initlen+1);
if (sh == NULL) return NULL;
- sh->len = initlen;
- sh->free = 0;
+ if (!init)
+ memset(sh, 0, hdrlen+initlen+1);
+ s = (char*)sh+hdrlen;
+ fp = ((unsigned char*)s)-1;
+ switch(type) {
+ case SDS_TYPE_5: {
+ *fp = type | (initlen << SDS_TYPE_BITS);
+ break;
+ }
+ case SDS_TYPE_8: {
+ SDS_HDR_VAR(8,s);
+ sh->len = initlen;
+ sh->alloc = initlen;
+ *fp = type;
+ break;
+ }
+ case SDS_TYPE_16: {
+ SDS_HDR_VAR(16,s);
+ sh->len = initlen;
+ sh->alloc = initlen;
+ *fp = type;
+ break;
+ }
+ case SDS_TYPE_32: {
+ SDS_HDR_VAR(32,s);
+ sh->len = initlen;
+ sh->alloc = initlen;
+ *fp = type;
+ break;
+ }
+ case SDS_TYPE_64: {
+ SDS_HDR_VAR(64,s);
+ sh->len = initlen;
+ sh->alloc = initlen;
+ *fp = type;
+ break;
+ }
+ }
if (initlen && init)
- memcpy(sh->buf, init, initlen);
- sh->buf[initlen] = '\0';
- return (char*)sh->buf;
+ memcpy(s, init, initlen);
+ s[initlen] = '\0';
+ return s;
}
/* Create an empty (zero length) sds string. Even in this case the string
@@ -71,7 +143,7 @@ sds sdsempty(void) {
return sdsnewlen("",0);
}
-/* Create a new sds string starting from a null termined C string. */
+/* Create a new sds string starting from a null terminated C string. */
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
@@ -85,7 +157,7 @@ sds sdsdup(const sds s) {
/* Free an sds string. No operation is performed if 's' is NULL. */
void sdsfree(sds s) {
if (s == NULL) return;
- free(s-sizeof(struct sdshdr));
+ s_free((char*)s-sdsHdrSize(s[-1]));
}
/* Set the sds string length to the length as obtained with strlen(), so
@@ -103,21 +175,17 @@ void sdsfree(sds s) {
* the output will be "6" as the string was modified but the logical length
* remains 6 bytes. */
void sdsupdatelen(sds s) {
- struct sdshdr *sh = (void*) (s-sizeof *sh);
- int reallen = strlen(s);
- sh->free += (sh->len-reallen);
- sh->len = reallen;
+ size_t reallen = strlen(s);
+ sdssetlen(s, reallen);
}
-/* Modify an sds string on-place to make it empty (zero length).
+/* Modify an sds string in-place to make it empty (zero length).
* However all the existing buffer is not discarded but set as free space
* so that next append operations will not require allocations up to the
* number of bytes previously available. */
void sdsclear(sds s) {
- struct sdshdr *sh = (void*) (s-sizeof *sh);
- sh->free += sh->len;
- sh->len = 0;
- sh->buf[0] = '\0';
+ sdssetlen(s, 0);
+ s[0] = '\0';
}
/* Enlarge the free space at the end of the sds string so that the caller
@@ -127,23 +195,50 @@ void sdsclear(sds s) {
* Note: this does not change the *length* of the sds string as returned
* by sdslen(), but only the free buffer space we have. */
sds sdsMakeRoomFor(sds s, size_t addlen) {
- struct sdshdr *sh, *newsh;
- size_t free = sdsavail(s);
- size_t len, newlen;
+ void *sh, *newsh;
+ size_t avail = sdsavail(s);
+ size_t len, newlen, reqlen;
+ char type, oldtype = s[-1] & SDS_TYPE_MASK;
+ int hdrlen;
+
+ /* Return ASAP if there is enough space left. */
+ if (avail >= addlen) return s;
- if (free >= addlen) return s;
len = sdslen(s);
- sh = (void*) (s-sizeof *sh);
- newlen = (len+addlen);
+ sh = (char*)s-sdsHdrSize(oldtype);
+ reqlen = newlen = (len+addlen);
+ if (newlen <= len) return NULL; /* Catch size_t overflow */
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
- newsh = realloc(sh, sizeof *newsh+newlen+1);
- if (newsh == NULL) return NULL;
- newsh->free = newlen - len;
- return newsh->buf;
+ type = sdsReqType(newlen);
+
+ /* Don't use type 5: the user is appending to the string and type 5 is
+ * not able to remember empty space, so sdsMakeRoomFor() must be called
+ * at every appending operation. */
+ if (type == SDS_TYPE_5) type = SDS_TYPE_8;
+
+ hdrlen = sdsHdrSize(type);
+ if (hdrlen+newlen+1 <= reqlen) return NULL; /* Catch size_t overflow */
+ if (oldtype==type) {
+ newsh = s_realloc(sh, hdrlen+newlen+1);
+ if (newsh == NULL) return NULL;
+ s = (char*)newsh+hdrlen;
+ } else {
+ /* Since the header size changes, need to move the string forward,
+ * and can't use realloc */
+ newsh = s_malloc(hdrlen+newlen+1);
+ if (newsh == NULL) return NULL;
+ memcpy((char*)newsh+hdrlen, s, len+1);
+ s_free(sh);
+ s = (char*)newsh+hdrlen;
+ s[-1] = type;
+ sdssetlen(s, len);
+ }
+ sdssetalloc(s, newlen);
+ return s;
}
/* Reallocate the sds string so that it has no free space at the end. The
@@ -153,15 +248,32 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
* After the call, the passed sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdsRemoveFreeSpace(sds s) {
- struct sdshdr *sh;
-
- sh = (void*) (s-sizeof *sh);
- sh = realloc(sh, sizeof *sh+sh->len+1);
- sh->free = 0;
- return sh->buf;
+ void *sh, *newsh;
+ char type, oldtype = s[-1] & SDS_TYPE_MASK;
+ int hdrlen;
+ size_t len = sdslen(s);
+ sh = (char*)s-sdsHdrSize(oldtype);
+
+ type = sdsReqType(len);
+ hdrlen = sdsHdrSize(type);
+ if (oldtype==type) {
+ newsh = s_realloc(sh, hdrlen+len+1);
+ if (newsh == NULL) return NULL;
+ s = (char*)newsh+hdrlen;
+ } else {
+ newsh = s_malloc(hdrlen+len+1);
+ if (newsh == NULL) return NULL;
+ memcpy((char*)newsh+hdrlen, s, len+1);
+ s_free(sh);
+ s = (char*)newsh+hdrlen;
+ s[-1] = type;
+ sdssetlen(s, len);
+ }
+ sdssetalloc(s, len);
+ return s;
}
-/* Return the total size of the allocation of the specified sds string,
+/* Return the total size of the allocation of the specifed sds string,
* including:
* 1) The sds header before the pointer.
* 2) The string.
@@ -169,9 +281,14 @@ sds sdsRemoveFreeSpace(sds s) {
* 4) The implicit null term.
*/
size_t sdsAllocSize(sds s) {
- struct sdshdr *sh = (void*) (s-sizeof *sh);
+ size_t alloc = sdsalloc(s);
+ return sdsHdrSize(s[-1])+alloc+1;
+}
- return sizeof(*sh)+sh->len+sh->free+1;
+/* Return the pointer of the actual SDS allocation (normally SDS strings
+ * are referenced by the start of the string buffer). */
+void *sdsAllocPtr(sds s) {
+ return (void*) (s-sdsHdrSize(s[-1]));
}
/* Increment the sds length and decrements the left free space at the
@@ -198,13 +315,44 @@ size_t sdsAllocSize(sds s) {
* sdsIncrLen(s, nread);
*/
void sdsIncrLen(sds s, int incr) {
- struct sdshdr *sh = (void*) (s-sizeof *sh);
-
- assert(sh->free >= incr);
- sh->len += incr;
- sh->free -= incr;
- assert(sh->free >= 0);
- s[sh->len] = '\0';
+ unsigned char flags = s[-1];
+ size_t len;
+ switch(flags&SDS_TYPE_MASK) {
+ case SDS_TYPE_5: {
+ unsigned char *fp = ((unsigned char*)s)-1;
+ unsigned char oldlen = SDS_TYPE_5_LEN(flags);
+ assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr)));
+ *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS);
+ len = oldlen+incr;
+ break;
+ }
+ case SDS_TYPE_8: {
+ SDS_HDR_VAR(8,s);
+ assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
+ len = (sh->len += incr);
+ break;
+ }
+ case SDS_TYPE_16: {
+ SDS_HDR_VAR(16,s);
+ assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
+ len = (sh->len += incr);
+ break;
+ }
+ case SDS_TYPE_32: {
+ SDS_HDR_VAR(32,s);
+ assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
+ len = (sh->len += incr);
+ break;
+ }
+ case SDS_TYPE_64: {
+ SDS_HDR_VAR(64,s);
+ assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr)));
+ len = (sh->len += incr);
+ break;
+ }
+ default: len = 0; /* Just to avoid compilation warnings. */
+ }
+ s[len] = '\0';
}
/* Grow the sds to have the specified length. Bytes that were not part of
@@ -213,19 +361,15 @@ void sdsIncrLen(sds s, int incr) {
* if the specified length is smaller than the current length, no operation
* is performed. */
sds sdsgrowzero(sds s, size_t len) {
- struct sdshdr *sh = (void*) (s-sizeof *sh);
- size_t totlen, curlen = sh->len;
+ size_t curlen = sdslen(s);
if (len <= curlen) return s;
s = sdsMakeRoomFor(s,len-curlen);
if (s == NULL) return NULL;
/* Make sure added region doesn't contain garbage */
- sh = (void*)(s-sizeof *sh);
memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */
- totlen = sh->len+sh->free;
- sh->len = len;
- sh->free = totlen-sh->len;
+ sdssetlen(s, len);
return s;
}
@@ -235,15 +379,12 @@ sds sdsgrowzero(sds s, size_t len) {
* After the call, the passed sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdscatlen(sds s, const void *t, size_t len) {
- struct sdshdr *sh;
size_t curlen = sdslen(s);
s = sdsMakeRoomFor(s,len);
if (s == NULL) return NULL;
- sh = (void*) (s-sizeof *sh);
memcpy(s+curlen, t, len);
- sh->len = curlen+len;
- sh->free = sh->free-len;
+ sdssetlen(s, curlen+len);
s[curlen+len] = '\0';
return s;
}
@@ -267,23 +408,17 @@ sds sdscatsds(sds s, const sds t) {
/* Destructively modify the sds string 's' to hold the specified binary
* safe string pointed by 't' of length 'len' bytes. */
sds sdscpylen(sds s, const char *t, size_t len) {
- struct sdshdr *sh = (void*) (s-sizeof *sh);
- size_t totlen = sh->free+sh->len;
-
- if (totlen < len) {
- s = sdsMakeRoomFor(s,len-sh->len);
+ if (sdsalloc(s) < len) {
+ s = sdsMakeRoomFor(s,len-sdslen(s));
if (s == NULL) return NULL;
- sh = (void*) (s-sizeof *sh);
- totlen = sh->free+sh->len;
}
memcpy(s, t, len);
s[len] = '\0';
- sh->len = len;
- sh->free = totlen-len;
+ sdssetlen(s, len);
return s;
}
-/* Like sdscpylen() but 't' must be a null-termined string so that the length
+/* Like sdscpylen() but 't' must be a null-terminated string so that the length
* of the string is obtained with strlen(). */
sds sdscpy(sds s, const char *t) {
return sdscpylen(s, t, strlen(t));
@@ -356,27 +491,52 @@ int sdsull2str(char *s, unsigned long long v) {
return l;
}
-/* Like sdscatpritf() but gets va_list instead of being variadic. */
+/* Create an sds string from a long long value. It is much faster than:
+ *
+ * sdscatprintf(sdsempty(),"%lld\n", value);
+ */
+sds sdsfromlonglong(long long value) {
+ char buf[SDS_LLSTR_SIZE];
+ int len = sdsll2str(buf,value);
+
+ return sdsnewlen(buf,len);
+}
+
+/* Like sdscatprintf() but gets va_list instead of being variadic. */
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
va_list cpy;
- char *buf, *t;
- size_t buflen = 16;
+ char staticbuf[1024], *buf = staticbuf, *t;
+ size_t buflen = strlen(fmt)*2;
- while(1) {
- buf = malloc(buflen);
+ /* We try to start using a static buffer for speed.
+ * If not possible we revert to heap allocation. */
+ if (buflen > sizeof(staticbuf)) {
+ buf = s_malloc(buflen);
if (buf == NULL) return NULL;
+ } else {
+ buflen = sizeof(staticbuf);
+ }
+
+ /* Try with buffers two times bigger every time we fail to
+ * fit the string in the current buffer size. */
+ while(1) {
buf[buflen-2] = '\0';
va_copy(cpy,ap);
vsnprintf(buf, buflen, fmt, cpy);
+ va_end(cpy);
if (buf[buflen-2] != '\0') {
- free(buf);
+ if (buf != staticbuf) s_free(buf);
buflen *= 2;
+ buf = s_malloc(buflen);
+ if (buf == NULL) return NULL;
continue;
}
break;
}
+
+ /* Finally concat the obtained string to the SDS string and return it. */
t = sdscat(s, buf);
- free(buf);
+ if (buf != staticbuf) s_free(buf);
return t;
}
@@ -389,7 +549,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
* Example:
*
* s = sdsnew("Sum is: ");
- * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b);
+ * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b).
*
* Often you need to create a string from scratch with the printf-alike
* format. When this is the need, just use sdsempty() as the target string:
@@ -419,29 +579,25 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
* %I - 64 bit signed integer (long long, int64_t)
* %u - unsigned int
* %U - 64 bit unsigned integer (unsigned long long, uint64_t)
- * %T - A size_t variable.
* %% - Verbatim "%" character.
*/
sds sdscatfmt(sds s, char const *fmt, ...) {
- struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
- size_t initlen = sdslen(s);
const char *f = fmt;
- int i;
+ long i;
va_list ap;
va_start(ap,fmt);
- f = fmt; /* Next format specifier byte to process. */
- i = initlen; /* Position of the next byte to write to dest str. */
+ i = sdslen(s); /* Position of the next byte to write to dest str. */
while(*f) {
char next, *str;
- int l;
+ size_t l;
long long num;
unsigned long long unum;
/* Make sure there is always space for at least 1 char. */
- if (sh->free == 0) {
+ if (sdsavail(s)==0) {
s = sdsMakeRoomFor(s,1);
- sh = (void*) (s-(sizeof(struct sdshdr)));
+ if (s == NULL) goto fmt_error;
}
switch(*f) {
@@ -453,13 +609,12 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
case 'S':
str = va_arg(ap,char*);
l = (next == 's') ? strlen(str) : sdslen(str);
- if (sh->free < l) {
+ if (sdsavail(s) < l) {
s = sdsMakeRoomFor(s,l);
- sh = (void*) (s-(sizeof(struct sdshdr)));
+ if (s == NULL) goto fmt_error;
}
memcpy(s+i,str,l);
- sh->len += l;
- sh->free -= l;
+ sdsinclen(s,l);
i += l;
break;
case 'i':
@@ -471,49 +626,42 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
{
char buf[SDS_LLSTR_SIZE];
l = sdsll2str(buf,num);
- if (sh->free < l) {
+ if (sdsavail(s) < l) {
s = sdsMakeRoomFor(s,l);
- sh = (void*) (s-(sizeof(struct sdshdr)));
+ if (s == NULL) goto fmt_error;
}
memcpy(s+i,buf,l);
- sh->len += l;
- sh->free -= l;
+ sdsinclen(s,l);
i += l;
}
break;
case 'u':
case 'U':
- case 'T':
if (next == 'u')
unum = va_arg(ap,unsigned int);
- else if(next == 'U')
- unum = va_arg(ap,unsigned long long);
else
- unum = (unsigned long long)va_arg(ap,size_t);
+ unum = va_arg(ap,unsigned long long);
{
char buf[SDS_LLSTR_SIZE];
l = sdsull2str(buf,unum);
- if (sh->free < l) {
+ if (sdsavail(s) < l) {
s = sdsMakeRoomFor(s,l);
- sh = (void*) (s-(sizeof(struct sdshdr)));
+ if (s == NULL) goto fmt_error;
}
memcpy(s+i,buf,l);
- sh->len += l;
- sh->free -= l;
+ sdsinclen(s,l);
i += l;
}
break;
default: /* Handle %% and generally %<unknown>. */
s[i++] = next;
- sh->len += 1;
- sh->free -= 1;
+ sdsinclen(s,1);
break;
}
break;
default:
s[i++] = *f;
- sh->len += 1;
- sh->free -= 1;
+ sdsinclen(s,1);
break;
}
f++;
@@ -523,8 +671,11 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
/* Add null-term */
s[i] = '\0';
return s;
-}
+fmt_error:
+ va_end(ap);
+ return NULL;
+}
/* Remove the part of the string from left and from right composed just of
* contiguous characters found in 'cset', that is a null terminted C string.
@@ -535,25 +686,24 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
* Example:
*
* s = sdsnew("AA...AA.a.aa.aHelloWorld :::");
- * s = sdstrim(s,"A. :");
+ * s = sdstrim(s,"Aa. :");
* printf("%s\n", s);
*
* Output will be just "Hello World".
*/
-void sdstrim(sds s, const char *cset) {
- struct sdshdr *sh = (void*) (s-sizeof *sh);
+sds sdstrim(sds s, const char *cset) {
char *start, *end, *sp, *ep;
size_t len;
sp = start = s;
ep = end = s+sdslen(s)-1;
while(sp <= end && strchr(cset, *sp)) sp++;
- while(ep > start && strchr(cset, *ep)) ep--;
+ while(ep > sp && strchr(cset, *ep)) ep--;
len = (sp > ep) ? 0 : ((ep-sp)+1);
- if (sh->buf != sp) memmove(sh->buf, sp, len);
- sh->buf[len] = '\0';
- sh->free = sh->free+(sh->len-len);
- sh->len = len;
+ if (s != sp) memmove(s, sp, len);
+ s[len] = '\0';
+ sdssetlen(s,len);
+ return s;
}
/* Turn the string into a smaller (or equal) string containing only the
@@ -567,16 +717,20 @@ void sdstrim(sds s, const char *cset) {
*
* The string is modified in-place.
*
+ * Return value:
+ * -1 (error) if sdslen(s) is larger than maximum positive ssize_t value.
+ * 0 on success.
+ *
* Example:
*
* s = sdsnew("Hello World");
* sdsrange(s,1,-1); => "ello World"
*/
-void sdsrange(sds s, int start, int end) {
- struct sdshdr *sh = (void*) (s-sizeof *sh);
+int sdsrange(sds s, ssize_t start, ssize_t end) {
size_t newlen, len = sdslen(s);
+ if (len > SSIZE_MAX) return -1;
- if (len == 0) return;
+ if (len == 0) return 0;
if (start < 0) {
start = len+start;
if (start < 0) start = 0;
@@ -587,31 +741,31 @@ void sdsrange(sds s, int start, int end) {
}
newlen = (start > end) ? 0 : (end-start)+1;
if (newlen != 0) {
- if (start >= (signed)len) {
+ if (start >= (ssize_t)len) {
newlen = 0;
- } else if (end >= (signed)len) {
+ } else if (end >= (ssize_t)len) {
end = len-1;
newlen = (start > end) ? 0 : (end-start)+1;
}
} else {
start = 0;
}
- if (start && newlen) memmove(sh->buf, sh->buf+start, newlen);
- sh->buf[newlen] = 0;
- sh->free = sh->free+(sh->len-newlen);
- sh->len = newlen;
+ if (start && newlen) memmove(s, s+start, newlen);
+ s[newlen] = 0;
+ sdssetlen(s,newlen);
+ return 0;
}
/* Apply tolower() to every character of the sds string 's'. */
void sdstolower(sds s) {
- int len = sdslen(s), j;
+ size_t len = sdslen(s), j;
for (j = 0; j < len; j++) s[j] = tolower(s[j]);
}
/* Apply toupper() to every character of the sds string 's'. */
void sdstoupper(sds s) {
- int len = sdslen(s), j;
+ size_t len = sdslen(s), j;
for (j = 0; j < len; j++) s[j] = toupper(s[j]);
}
@@ -620,8 +774,8 @@ void sdstoupper(sds s) {
*
* Return value:
*
- * 1 if s1 > s2.
- * -1 if s1 < s2.
+ * positive if s1 > s2.
+ * negative if s1 < s2.
* 0 if s1 and s2 are exactly the same binary string.
*
* If two strings share exactly the same prefix, but one of the two has
@@ -661,7 +815,7 @@ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count
if (seplen < 1 || len < 0) return NULL;
- tokens = malloc(sizeof(sds)*slots);
+ tokens = s_malloc(sizeof(sds)*slots);
if (tokens == NULL) return NULL;
if (len == 0) {
@@ -674,7 +828,7 @@ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count
sds *newtokens;
slots *= 2;
- newtokens = realloc(tokens,sizeof(sds)*slots);
+ newtokens = s_realloc(tokens,sizeof(sds)*slots);
if (newtokens == NULL) goto cleanup;
tokens = newtokens;
}
@@ -698,7 +852,7 @@ cleanup:
{
int i;
for (i = 0; i < elements; i++) sdsfree(tokens[i]);
- free(tokens);
+ s_free(tokens);
*count = 0;
return NULL;
}
@@ -709,26 +863,7 @@ void sdsfreesplitres(sds *tokens, int count) {
if (!tokens) return;
while(count--)
sdsfree(tokens[count]);
- free(tokens);
-}
-
-/* Create an sds string from a long long value. It is much faster than:
- *
- * sdscatprintf(sdsempty(),"%lld\n", value);
- */
-sds sdsfromlonglong(long long value) {
- char buf[32], *p;
- unsigned long long v;
-
- v = (value < 0) ? -value : value;
- p = buf+31; /* point to the last character */
- do {
- *p-- = '0'+(v%10);
- v /= 10;
- } while(v);
- if (value < 0) *p-- = '-';
- p++;
- return sdsnewlen(p,32-(p-buf));
+ s_free(tokens);
}
/* Append to the sds string "s" an escaped string representation where
@@ -751,7 +886,7 @@ sds sdscatrepr(sds s, const char *p, size_t len) {
case '\a': s = sdscatlen(s,"\\a",2); break;
case '\b': s = sdscatlen(s,"\\b",2); break;
default:
- if (isprint(*p))
+ if (isprint((int) *p))
s = sdscatprintf(s,"%c",*p);
else
s = sdscatprintf(s,"\\x%02x",(unsigned char)*p);
@@ -762,13 +897,6 @@ sds sdscatrepr(sds s, const char *p, size_t len) {
return sdscatlen(s,"\"",1);
}
-/* Helper function for sdssplitargs() that returns non zero if 'c'
- * is a valid hex digit. */
-int is_hex_digit(char c) {
- return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
- (c >= 'A' && c <= 'F');
-}
-
/* Helper function for sdssplitargs() that converts a hex digit into an
* integer from 0 to 15 */
int hex_digit_to_int(char c) {
@@ -820,7 +948,7 @@ sds *sdssplitargs(const char *line, int *argc) {
*argc = 0;
while(1) {
/* skip blanks */
- while(*p && isspace(*p)) p++;
+ while(*p && isspace((int) *p)) p++;
if (*p) {
/* get a token */
int inq=0; /* set to 1 if we are in "quotes" */
@@ -831,8 +959,8 @@ sds *sdssplitargs(const char *line, int *argc) {
while(!done) {
if (inq) {
if (*p == '\\' && *(p+1) == 'x' &&
- is_hex_digit(*(p+2)) &&
- is_hex_digit(*(p+3)))
+ isxdigit((int) *(p+2)) &&
+ isxdigit((int) *(p+3)))
{
unsigned char byte;
@@ -856,7 +984,7 @@ sds *sdssplitargs(const char *line, int *argc) {
} else if (*p == '"') {
/* closing quote must be followed by a space or
* nothing at all. */
- if (*(p+1) && !isspace(*(p+1))) goto err;
+ if (*(p+1) && !isspace((int) *(p+1))) goto err;
done=1;
} else if (!*p) {
/* unterminated quotes */
@@ -871,7 +999,7 @@ sds *sdssplitargs(const char *line, int *argc) {
} else if (*p == '\'') {
/* closing quote must be followed by a space or
* nothing at all. */
- if (*(p+1) && !isspace(*(p+1))) goto err;
+ if (*(p+1) && !isspace((int) *(p+1))) goto err;
done=1;
} else if (!*p) {
/* unterminated quotes */
@@ -902,13 +1030,21 @@ sds *sdssplitargs(const char *line, int *argc) {
if (*p) p++;
}
/* add the token to the vector */
- vector = realloc(vector,((*argc)+1)*sizeof(char*));
- vector[*argc] = current;
- (*argc)++;
- current = NULL;
+ {
+ char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
+ if (new_vector == NULL) {
+ s_free(vector);
+ return NULL;
+ }
+
+ vector = new_vector;
+ vector[*argc] = current;
+ (*argc)++;
+ current = NULL;
+ }
} else {
/* Even on empty input string return something not NULL. */
- if (vector == NULL) vector = malloc(sizeof(void*));
+ if (vector == NULL) vector = s_malloc(sizeof(void*));
return vector;
}
}
@@ -916,7 +1052,7 @@ sds *sdssplitargs(const char *line, int *argc) {
err:
while((*argc)--)
sdsfree(vector[*argc]);
- free(vector);
+ s_free(vector);
if (current) sdsfree(current);
*argc = 0;
return NULL;
@@ -947,13 +1083,13 @@ sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) {
/* Join an array of C strings using the specified separator (also a C string).
* Returns the result as an sds string. */
-sds sdsjoin(char **argv, int argc, char *sep, size_t seplen) {
+sds sdsjoin(char **argv, int argc, char *sep) {
sds join = sdsempty();
int j;
for (j = 0; j < argc; j++) {
join = sdscat(join, argv[j]);
- if (j != argc-1) join = sdscatlen(join,sep,seplen);
+ if (j != argc-1) join = sdscat(join,sep);
}
return join;
}
@@ -970,13 +1106,23 @@ sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) {
return join;
}
-#ifdef SDS_TEST_MAIN
+/* Wrappers to the allocators used by SDS. Note that SDS will actually
+ * just use the macros defined into sdsalloc.h in order to avoid to pay
+ * the overhead of function calls. Here we define these wrappers only for
+ * the programs SDS is linked to, if they want to touch the SDS internals
+ * even if they use a different allocator. */
+void *sds_malloc(size_t size) { return s_malloc(size); }
+void *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); }
+void sds_free(void *ptr) { s_free(ptr); }
+
+#if defined(SDS_TEST_MAIN)
#include <stdio.h>
#include "testhelp.h"
+#include "limits.h"
-int main(void) {
+#define UNUSED(x) (void)(x)
+int sdsTest(void) {
{
- struct sdshdr *sh;
sds x = sdsnew("foo"), y;
test_cond("Create a string and obtain the length",
@@ -1003,7 +1149,35 @@ int main(void) {
sdsfree(x);
x = sdscatprintf(sdsempty(),"%d",123);
test_cond("sdscatprintf() seems working in the base case",
- sdslen(x) == 3 && memcmp(x,"123\0",4) ==0)
+ sdslen(x) == 3 && memcmp(x,"123\0",4) == 0)
+
+ sdsfree(x);
+ x = sdsnew("--");
+ x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX);
+ test_cond("sdscatfmt() seems working in the base case",
+ sdslen(x) == 60 &&
+ memcmp(x,"--Hello Hi! World -9223372036854775808,"
+ "9223372036854775807--",60) == 0)
+ printf("[%s]\n",x);
+
+ sdsfree(x);
+ x = sdsnew("--");
+ x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX);
+ test_cond("sdscatfmt() seems working with unsigned numbers",
+ sdslen(x) == 35 &&
+ memcmp(x,"--4294967295,18446744073709551615--",35) == 0)
+
+ sdsfree(x);
+ x = sdsnew(" x ");
+ sdstrim(x," x");
+ test_cond("sdstrim() works when all chars match",
+ sdslen(x) == 0)
+
+ sdsfree(x);
+ x = sdsnew(" x ");
+ sdstrim(x," ");
+ test_cond("sdstrim() works when a single char remains",
+ sdslen(x) == 1 && x[0] == 'x')
sdsfree(x);
x = sdsnew("xxciaoyyy");
@@ -1072,24 +1246,47 @@ int main(void) {
memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0)
{
- int oldfree;
+ unsigned int oldfree;
+ char *p;
+ int step = 10, j, i;
sdsfree(x);
+ sdsfree(y);
x = sdsnew("0");
- sh = (void*) (x-(sizeof(struct sdshdr)));
- test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0);
- x = sdsMakeRoomFor(x,1);
- sh = (void*) (x-(sizeof(struct sdshdr)));
- test_cond("sdsMakeRoomFor()", sh->len == 1 && sh->free > 0);
- oldfree = sh->free;
- x[1] = '1';
- sdsIncrLen(x,1);
- test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1');
- test_cond("sdsIncrLen() -- len", sh->len == 2);
- test_cond("sdsIncrLen() -- free", sh->free == oldfree-1);
+ test_cond("sdsnew() free/len buffers", sdslen(x) == 1 && sdsavail(x) == 0);
+
+ /* Run the test a few times in order to hit the first two
+ * SDS header types. */
+ for (i = 0; i < 10; i++) {
+ int oldlen = sdslen(x);
+ x = sdsMakeRoomFor(x,step);
+ int type = x[-1]&SDS_TYPE_MASK;
+
+ test_cond("sdsMakeRoomFor() len", sdslen(x) == oldlen);
+ if (type != SDS_TYPE_5) {
+ test_cond("sdsMakeRoomFor() free", sdsavail(x) >= step);
+ oldfree = sdsavail(x);
+ }
+ p = x+oldlen;
+ for (j = 0; j < step; j++) {
+ p[j] = 'A'+j;
+ }
+ sdsIncrLen(x,step);
+ }
+ test_cond("sdsMakeRoomFor() content",
+ memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0);
+ test_cond("sdsMakeRoomFor() final length",sdslen(x)==101);
+
+ sdsfree(x);
}
}
test_report()
return 0;
}
#endif
+
+#ifdef SDS_TEST_MAIN
+int main(void) {
+ return sdsTest();
+}
+#endif
diff --git a/contrib/hiredis/sds.h b/contrib/hiredis/sds.h
index a494f2efc..d9b67610f 100644
--- a/contrib/hiredis/sds.h
+++ b/contrib/hiredis/sds.h
@@ -1,6 +1,8 @@
-/* SDS (Simple Dynamic Strings), A C dynamic strings library.
+/* SDSLib 2.0 -- A C dynamic strings library
*
- * Copyright (c) 2006-2014, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2015, Oran Agra
+ * Copyright (c) 2015, Redis Labs, Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -32,38 +34,198 @@
#define __SDS_H
#define SDS_MAX_PREALLOC (1024*1024)
+#ifdef _MSC_VER
+typedef long long ssize_t;
+#define SSIZE_MAX (LLONG_MAX >> 1)
+#ifndef __clang__
+#define __attribute__(x)
+#endif
+#endif
#include <sys/types.h>
#include <stdarg.h>
-#ifdef _MSC_VER
-#include "win32.h"
-#endif
+#include <stdint.h>
typedef char *sds;
-struct sdshdr {
- int len;
- int free;
+/* Note: sdshdr5 is never used, we just access the flags byte directly.
+ * However is here to document the layout of type 5 SDS strings. */
+struct __attribute__ ((__packed__)) sdshdr5 {
+ unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
+ char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr8 {
+ uint8_t len; /* used */
+ uint8_t alloc; /* excluding the header and null terminator */
+ unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
+struct __attribute__ ((__packed__)) sdshdr16 {
+ uint16_t len; /* used */
+ uint16_t alloc; /* excluding the header and null terminator */
+ unsigned char flags; /* 3 lsb of type, 5 unused bits */
+ char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr32 {
+ uint32_t len; /* used */
+ uint32_t alloc; /* excluding the header and null terminator */
+ unsigned char flags; /* 3 lsb of type, 5 unused bits */
+ char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr64 {
+ uint64_t len; /* used */
+ uint64_t alloc; /* excluding the header and null terminator */
+ unsigned char flags; /* 3 lsb of type, 5 unused bits */
+ char buf[];
+};
+
+#define SDS_TYPE_5 0
+#define SDS_TYPE_8 1
+#define SDS_TYPE_16 2
+#define SDS_TYPE_32 3
+#define SDS_TYPE_64 4
+#define SDS_TYPE_MASK 7
+#define SDS_TYPE_BITS 3
+#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)));
+#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
+#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
static inline size_t sdslen(const sds s) {
- struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh);
- return sh->len;
+ unsigned char flags = s[-1];
+ switch(flags&SDS_TYPE_MASK) {
+ case SDS_TYPE_5:
+ return SDS_TYPE_5_LEN(flags);
+ case SDS_TYPE_8:
+ return SDS_HDR(8,s)->len;
+ case SDS_TYPE_16:
+ return SDS_HDR(16,s)->len;
+ case SDS_TYPE_32:
+ return SDS_HDR(32,s)->len;
+ case SDS_TYPE_64:
+ return SDS_HDR(64,s)->len;
+ }
+ return 0;
}
static inline size_t sdsavail(const sds s) {
- struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh);
- return sh->free;
+ unsigned char flags = s[-1];
+ switch(flags&SDS_TYPE_MASK) {
+ case SDS_TYPE_5: {
+ return 0;
+ }
+ case SDS_TYPE_8: {
+ SDS_HDR_VAR(8,s);
+ return sh->alloc - sh->len;
+ }
+ case SDS_TYPE_16: {
+ SDS_HDR_VAR(16,s);
+ return sh->alloc - sh->len;
+ }
+ case SDS_TYPE_32: {
+ SDS_HDR_VAR(32,s);
+ return sh->alloc - sh->len;
+ }
+ case SDS_TYPE_64: {
+ SDS_HDR_VAR(64,s);
+ return sh->alloc - sh->len;
+ }
+ }
+ return 0;
+}
+
+static inline void sdssetlen(sds s, size_t newlen) {
+ unsigned char flags = s[-1];
+ switch(flags&SDS_TYPE_MASK) {
+ case SDS_TYPE_5:
+ {
+ unsigned char *fp = ((unsigned char*)s)-1;
+ *fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS));
+ }
+ break;
+ case SDS_TYPE_8:
+ SDS_HDR(8,s)->len = (uint8_t)newlen;
+ break;
+ case SDS_TYPE_16:
+ SDS_HDR(16,s)->len = (uint16_t)newlen;
+ break;
+ case SDS_TYPE_32:
+ SDS_HDR(32,s)->len = (uint32_t)newlen;
+ break;
+ case SDS_TYPE_64:
+ SDS_HDR(64,s)->len = (uint64_t)newlen;
+ break;
+ }
+}
+
+static inline void sdsinclen(sds s, size_t inc) {
+ unsigned char flags = s[-1];
+ switch(flags&SDS_TYPE_MASK) {
+ case SDS_TYPE_5:
+ {
+ unsigned char *fp = ((unsigned char*)s)-1;
+ unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc;
+ *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
+ }
+ break;
+ case SDS_TYPE_8:
+ SDS_HDR(8,s)->len += (uint8_t)inc;
+ break;
+ case SDS_TYPE_16:
+ SDS_HDR(16,s)->len += (uint16_t)inc;
+ break;
+ case SDS_TYPE_32:
+ SDS_HDR(32,s)->len += (uint32_t)inc;
+ break;
+ case SDS_TYPE_64:
+ SDS_HDR(64,s)->len += (uint64_t)inc;
+ break;
+ }
+}
+
+/* sdsalloc() = sdsavail() + sdslen() */
+static inline size_t sdsalloc(const sds s) {
+ unsigned char flags = s[-1];
+ switch(flags&SDS_TYPE_MASK) {
+ case SDS_TYPE_5:
+ return SDS_TYPE_5_LEN(flags);
+ case SDS_TYPE_8:
+ return SDS_HDR(8,s)->alloc;
+ case SDS_TYPE_16:
+ return SDS_HDR(16,s)->alloc;
+ case SDS_TYPE_32:
+ return SDS_HDR(32,s)->alloc;
+ case SDS_TYPE_64:
+ return SDS_HDR(64,s)->alloc;
+ }
+ return 0;
+}
+
+static inline void sdssetalloc(sds s, size_t newlen) {
+ unsigned char flags = s[-1];
+ switch(flags&SDS_TYPE_MASK) {
+ case SDS_TYPE_5:
+ /* Nothing to do, this type has no total allocation info. */
+ break;
+ case SDS_TYPE_8:
+ SDS_HDR(8,s)->alloc = (uint8_t)newlen;
+ break;
+ case SDS_TYPE_16:
+ SDS_HDR(16,s)->alloc = (uint16_t)newlen;
+ break;
+ case SDS_TYPE_32:
+ SDS_HDR(32,s)->alloc = (uint32_t)newlen;
+ break;
+ case SDS_TYPE_64:
+ SDS_HDR(64,s)->alloc = (uint64_t)newlen;
+ break;
+ }
}
sds sdsnewlen(const void *init, size_t initlen);
sds sdsnew(const char *init);
sds sdsempty(void);
-size_t sdslen(const sds s);
sds sdsdup(const sds s);
void sdsfree(sds s);
-size_t sdsavail(const sds s);
sds sdsgrowzero(sds s, size_t len);
sds sdscatlen(sds s, const void *t, size_t len);
sds sdscat(sds s, const char *t);
@@ -71,9 +233,6 @@ sds sdscatsds(sds s, const sds t);
sds sdscpylen(sds s, const char *t, size_t len);
sds sdscpy(sds s, const char *t);
-#ifdef __GNUC__
-__attribute__((format(printf, 2, 0)))
-#endif
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
#ifdef __GNUC__
sds sdscatprintf(sds s, const char *fmt, ...)
@@ -83,8 +242,8 @@ sds sdscatprintf(sds s, const char *fmt, ...);
#endif
sds sdscatfmt(sds s, char const *fmt, ...);
-void sdstrim(sds s, const char *cset);
-void sdsrange(sds s, int start, int end);
+sds sdstrim(sds s, const char *cset);
+int sdsrange(sds s, ssize_t start, ssize_t end);
void sdsupdatelen(sds s);
void sdsclear(sds s);
int sdscmp(const sds s1, const sds s2);
@@ -96,7 +255,7 @@ sds sdsfromlonglong(long long value);
sds sdscatrepr(sds s, const char *p, size_t len);
sds *sdssplitargs(const char *line, int *argc);
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
-sds sdsjoin(char **argv, int argc, char *sep, size_t seplen);
+sds sdsjoin(char **argv, int argc, char *sep);
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
/* Low level functions exposed to the user API */
@@ -104,5 +263,18 @@ sds sdsMakeRoomFor(sds s, size_t addlen);
void sdsIncrLen(sds s, int incr);
sds sdsRemoveFreeSpace(sds s);
size_t sdsAllocSize(sds s);
+void *sdsAllocPtr(sds s);
+
+/* Export the allocator used by SDS to the program using SDS.
+ * Sometimes the program SDS is linked to, may use a different set of
+ * allocators, but may want to allocate or free things that SDS will
+ * respectively free or allocate. */
+void *sds_malloc(size_t size);
+void *sds_realloc(void *ptr, size_t size);
+void sds_free(void *ptr);
+
+#ifdef REDIS_TEST
+int sdsTest(int argc, char *argv[]);
+#endif
#endif
diff --git a/contrib/hiredis/sdsalloc.h b/contrib/hiredis/sdsalloc.h
new file mode 100644
index 000000000..5538dd94c
--- /dev/null
+++ b/contrib/hiredis/sdsalloc.h
@@ -0,0 +1,44 @@
+/* SDSLib 2.0 -- A C dynamic strings library
+ *
+ * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2015, Oran Agra
+ * Copyright (c) 2015, Redis Labs, Inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* SDS allocator selection.
+ *
+ * This file is used in order to change the SDS allocator at compile time.
+ * Just define the following defines to what you want to use. Also add
+ * the include of your alternate allocator if needed (not needed in order
+ * to use the default libc allocator). */
+
+#include "alloc.h"
+
+#define s_malloc hi_malloc
+#define s_realloc hi_realloc
+#define s_free hi_free
diff --git a/contrib/hiredis/sockcompat.c b/contrib/hiredis/sockcompat.c
new file mode 100644
index 000000000..378745f4b
--- /dev/null
+++ b/contrib/hiredis/sockcompat.c
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define REDIS_SOCKCOMPAT_IMPLEMENTATION
+#include "sockcompat.h"
+
+#ifdef _WIN32
+static int _wsaErrorToErrno(int err) {
+ switch (err) {
+ case WSAEWOULDBLOCK:
+ return EWOULDBLOCK;
+ case WSAEINPROGRESS:
+ return EINPROGRESS;
+ case WSAEALREADY:
+ return EALREADY;
+ case WSAENOTSOCK:
+ return ENOTSOCK;
+ case WSAEDESTADDRREQ:
+ return EDESTADDRREQ;
+ case WSAEMSGSIZE:
+ return EMSGSIZE;
+ case WSAEPROTOTYPE:
+ return EPROTOTYPE;
+ case WSAENOPROTOOPT:
+ return ENOPROTOOPT;
+ case WSAEPROTONOSUPPORT:
+ return EPROTONOSUPPORT;
+ case WSAEOPNOTSUPP:
+ return EOPNOTSUPP;
+ case WSAEAFNOSUPPORT:
+ return EAFNOSUPPORT;
+ case WSAEADDRINUSE:
+ return EADDRINUSE;
+ case WSAEADDRNOTAVAIL:
+ return EADDRNOTAVAIL;
+ case WSAENETDOWN:
+ return ENETDOWN;
+ case WSAENETUNREACH:
+ return ENETUNREACH;
+ case WSAENETRESET:
+ return ENETRESET;
+ case WSAECONNABORTED:
+ return ECONNABORTED;
+ case WSAECONNRESET:
+ return ECONNRESET;
+ case WSAENOBUFS:
+ return ENOBUFS;
+ case WSAEISCONN:
+ return EISCONN;
+ case WSAENOTCONN:
+ return ENOTCONN;
+ case WSAETIMEDOUT:
+ return ETIMEDOUT;
+ case WSAECONNREFUSED:
+ return ECONNREFUSED;
+ case WSAELOOP:
+ return ELOOP;
+ case WSAENAMETOOLONG:
+ return ENAMETOOLONG;
+ case WSAEHOSTUNREACH:
+ return EHOSTUNREACH;
+ case WSAENOTEMPTY:
+ return ENOTEMPTY;
+ default:
+ /* We just return a generic I/O error if we could not find a relevant error. */
+ return EIO;
+ }
+}
+
+static void _updateErrno(int success) {
+ errno = success ? 0 : _wsaErrorToErrno(WSAGetLastError());
+}
+
+static int _initWinsock() {
+ static int s_initialized = 0;
+ if (!s_initialized) {
+ static WSADATA wsadata;
+ int err = WSAStartup(MAKEWORD(2,2), &wsadata);
+ if (err != 0) {
+ errno = _wsaErrorToErrno(err);
+ return 0;
+ }
+ s_initialized = 1;
+ }
+ return 1;
+}
+
+int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) {
+ /* Note: This function is likely to be called before other functions, so run init here. */
+ if (!_initWinsock()) {
+ return EAI_FAIL;
+ }
+
+ switch (getaddrinfo(node, service, hints, res)) {
+ case 0: return 0;
+ case WSATRY_AGAIN: return EAI_AGAIN;
+ case WSAEINVAL: return EAI_BADFLAGS;
+ case WSAEAFNOSUPPORT: return EAI_FAMILY;
+ case WSA_NOT_ENOUGH_MEMORY: return EAI_MEMORY;
+ case WSAHOST_NOT_FOUND: return EAI_NONAME;
+ case WSATYPE_NOT_FOUND: return EAI_SERVICE;
+ case WSAESOCKTNOSUPPORT: return EAI_SOCKTYPE;
+ default: return EAI_FAIL; /* Including WSANO_RECOVERY */
+ }
+}
+
+const char *win32_gai_strerror(int errcode) {
+ switch (errcode) {
+ case 0: errcode = 0; break;
+ case EAI_AGAIN: errcode = WSATRY_AGAIN; break;
+ case EAI_BADFLAGS: errcode = WSAEINVAL; break;
+ case EAI_FAMILY: errcode = WSAEAFNOSUPPORT; break;
+ case EAI_MEMORY: errcode = WSA_NOT_ENOUGH_MEMORY; break;
+ case EAI_NONAME: errcode = WSAHOST_NOT_FOUND; break;
+ case EAI_SERVICE: errcode = WSATYPE_NOT_FOUND; break;
+ case EAI_SOCKTYPE: errcode = WSAESOCKTNOSUPPORT; break;
+ default: errcode = WSANO_RECOVERY; break; /* Including EAI_FAIL */
+ }
+ return gai_strerror(errcode);
+}
+
+void win32_freeaddrinfo(struct addrinfo *res) {
+ freeaddrinfo(res);
+}
+
+SOCKET win32_socket(int domain, int type, int protocol) {
+ SOCKET s;
+
+ /* Note: This function is likely to be called before other functions, so run init here. */
+ if (!_initWinsock()) {
+ return INVALID_SOCKET;
+ }
+
+ _updateErrno((s = socket(domain, type, protocol)) != INVALID_SOCKET);
+ return s;
+}
+
+int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp) {
+ int ret = ioctlsocket(fd, (long)request, argp);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
+ int ret = bind(sockfd, addr, addrlen);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
+ int ret = connect(sockfd, addr, addrlen);
+ _updateErrno(ret != SOCKET_ERROR);
+
+ /* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as
+ * EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX
+ * logic consistent.
+ * Additionally, WSAALREADY is can be reported as WSAEINVAL to and this is
+ * translated to EIO. Convert appropriately
+ */
+ int err = errno;
+ if (err == EWOULDBLOCK) {
+ errno = EINPROGRESS;
+ }
+ else if (err == EIO) {
+ errno = EALREADY;
+ }
+
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) {
+ int ret = 0;
+ if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
+ if (*optlen >= sizeof (struct timeval)) {
+ struct timeval *tv = optval;
+ DWORD timeout = 0;
+ socklen_t dwlen = 0;
+ ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen);
+ tv->tv_sec = timeout / 1000;
+ tv->tv_usec = (timeout * 1000) % 1000000;
+ } else {
+ ret = WSAEFAULT;
+ }
+ *optlen = sizeof (struct timeval);
+ } else {
+ ret = getsockopt(sockfd, level, optname, (char*)optval, optlen);
+ }
+ if (ret != SOCKET_ERROR && level == SOL_SOCKET && optname == SO_ERROR) {
+ /* translate SO_ERROR codes, if non-zero */
+ int err = *(int*)optval;
+ if (err != 0) {
+ err = _wsaErrorToErrno(err);
+ *(int*)optval = err;
+ }
+ }
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) {
+ int ret = 0;
+ if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
+ const struct timeval *tv = optval;
+ DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000;
+ ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD));
+ } else {
+ ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen);
+ }
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_close(SOCKET fd) {
+ int ret = closesocket(fd);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags) {
+ int ret = recv(sockfd, (char*)buf, (int)len, flags);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags) {
+ int ret = send(sockfd, (const char*)buf, (int)len, flags);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
+ int ret = WSAPoll(fds, nfds, timeout);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_redisKeepAlive(SOCKET sockfd, int interval_ms) {
+ struct tcp_keepalive cfg;
+ DWORD bytes_in;
+ int res;
+
+ cfg.onoff = 1;
+ cfg.keepaliveinterval = interval_ms;
+ cfg.keepalivetime = interval_ms;
+
+ res = WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, &cfg,
+ sizeof(struct tcp_keepalive), NULL, 0,
+ &bytes_in, NULL, NULL);
+
+ return res == 0 ? 0 : _wsaErrorToErrno(res);
+}
+
+#endif /* _WIN32 */
diff --git a/contrib/hiredis/sockcompat.h b/contrib/hiredis/sockcompat.h
new file mode 100644
index 000000000..6ca5d9fca
--- /dev/null
+++ b/contrib/hiredis/sockcompat.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __SOCKCOMPAT_H
+#define __SOCKCOMPAT_H
+
+#ifndef _WIN32
+/* For POSIX systems we use the standard BSD socket API. */
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <poll.h>
+#else
+/* For Windows we use winsock. */
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <stddef.h>
+#include <errno.h>
+#include <mstcpip.h>
+
+#ifdef _MSC_VER
+typedef long long ssize_t;
+#endif
+
+/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */
+int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
+const char *win32_gai_strerror(int errcode);
+void win32_freeaddrinfo(struct addrinfo *res);
+SOCKET win32_socket(int domain, int type, int protocol);
+int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp);
+int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
+int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
+int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen);
+int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen);
+int win32_close(SOCKET fd);
+ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags);
+ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags);
+typedef ULONG nfds_t;
+int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout);
+
+int win32_redisKeepAlive(SOCKET sockfd, int interval_ms);
+
+#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION
+#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res)
+#undef gai_strerror
+#define gai_strerror(errcode) win32_gai_strerror(errcode)
+#define freeaddrinfo(res) win32_freeaddrinfo(res)
+#define socket(domain, type, protocol) win32_socket(domain, type, protocol)
+#define ioctl(fd, request, argp) win32_ioctl(fd, request, argp)
+#define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen)
+#define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen)
+#define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen)
+#define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen)
+#define close(fd) win32_close(fd)
+#define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags)
+#define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags)
+#define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout)
+#endif /* REDIS_SOCKCOMPAT_IMPLEMENTATION */
+#endif /* _WIN32 */
+
+#endif /* __SOCKCOMPAT_H */
diff --git a/contrib/hiredis/ssl.c b/contrib/hiredis/ssl.c
new file mode 100644
index 000000000..21ff35932
--- /dev/null
+++ b/contrib/hiredis/ssl.c
@@ -0,0 +1,617 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2019, Redis Labs
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "hiredis.h"
+#include "async.h"
+#include "net.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#ifdef _WIN32
+#include <windows.h>
+#include <wincrypt.h>
+#ifdef OPENSSL_IS_BORINGSSL
+#undef X509_NAME
+#undef X509_EXTENSIONS
+#undef PKCS7_ISSUER_AND_SERIAL
+#undef PKCS7_SIGNER_INFO
+#undef OCSP_REQUEST
+#undef OCSP_RESPONSE
+#endif
+#else
+#include <pthread.h>
+#endif
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include "win32.h"
+#include "async_private.h"
+#include "hiredis_ssl.h"
+
+#define OPENSSL_1_1_0 0x10100000L
+
+void __redisSetError(redisContext *c, int type, const char *str);
+
+struct redisSSLContext {
+ /* Associated OpenSSL SSL_CTX as created by redisCreateSSLContext() */
+ SSL_CTX *ssl_ctx;
+
+ /* Requested SNI, or NULL */
+ char *server_name;
+};
+
+/* The SSL connection context is attached to SSL/TLS connections as a privdata. */
+typedef struct redisSSL {
+ /**
+ * OpenSSL SSL object.
+ */
+ SSL *ssl;
+
+ /**
+ * SSL_write() requires to be called again with the same arguments it was
+ * previously called with in the event of an SSL_read/SSL_write situation
+ */
+ size_t lastLen;
+
+ /** Whether the SSL layer requires read (possibly before a write) */
+ int wantRead;
+
+ /**
+ * Whether a write was requested prior to a read. If set, the write()
+ * should resume whenever a read takes place, if possible
+ */
+ int pendingWrite;
+} redisSSL;
+
+/* Forward declaration */
+redisContextFuncs redisContextSSLFuncs;
+
+/**
+ * OpenSSL global initialization and locking handling callbacks.
+ * Note that this is only required for OpenSSL < 1.1.0.
+ */
+
+#if OPENSSL_VERSION_NUMBER < OPENSSL_1_1_0
+#define HIREDIS_USE_CRYPTO_LOCKS
+#endif
+
+#ifdef HIREDIS_USE_CRYPTO_LOCKS
+#ifdef _WIN32
+typedef CRITICAL_SECTION sslLockType;
+static void sslLockInit(sslLockType* l) {
+ InitializeCriticalSection(l);
+}
+static void sslLockAcquire(sslLockType* l) {
+ EnterCriticalSection(l);
+}
+static void sslLockRelease(sslLockType* l) {
+ LeaveCriticalSection(l);
+}
+#else
+typedef pthread_mutex_t sslLockType;
+static void sslLockInit(sslLockType *l) {
+ pthread_mutex_init(l, NULL);
+}
+static void sslLockAcquire(sslLockType *l) {
+ pthread_mutex_lock(l);
+}
+static void sslLockRelease(sslLockType *l) {
+ pthread_mutex_unlock(l);
+}
+#endif
+
+static sslLockType* ossl_locks;
+
+static void opensslDoLock(int mode, int lkid, const char *f, int line) {
+ sslLockType *l = ossl_locks + lkid;
+
+ if (mode & CRYPTO_LOCK) {
+ sslLockAcquire(l);
+ } else {
+ sslLockRelease(l);
+ }
+
+ (void)f;
+ (void)line;
+}
+
+static int initOpensslLocks(void) {
+ unsigned ii, nlocks;
+ if (CRYPTO_get_locking_callback() != NULL) {
+ /* Someone already set the callback before us. Don't destroy it! */
+ return REDIS_OK;
+ }
+ nlocks = CRYPTO_num_locks();
+ ossl_locks = hi_malloc(sizeof(*ossl_locks) * nlocks);
+ if (ossl_locks == NULL)
+ return REDIS_ERR;
+
+ for (ii = 0; ii < nlocks; ii++) {
+ sslLockInit(ossl_locks + ii);
+ }
+ CRYPTO_set_locking_callback(opensslDoLock);
+ return REDIS_OK;
+}
+#endif /* HIREDIS_USE_CRYPTO_LOCKS */
+
+int redisInitOpenSSL(void)
+{
+ SSL_library_init();
+#ifdef HIREDIS_USE_CRYPTO_LOCKS
+ initOpensslLocks();
+#endif
+
+ return REDIS_OK;
+}
+
+/**
+ * redisSSLContext helper context destruction.
+ */
+
+const char *redisSSLContextGetError(redisSSLContextError error)
+{
+ switch (error) {
+ case REDIS_SSL_CTX_NONE:
+ return "No Error";
+ case REDIS_SSL_CTX_CREATE_FAILED:
+ return "Failed to create OpenSSL SSL_CTX";
+ case REDIS_SSL_CTX_CERT_KEY_REQUIRED:
+ return "Client cert and key must both be specified or skipped";
+ case REDIS_SSL_CTX_CA_CERT_LOAD_FAILED:
+ return "Failed to load CA Certificate or CA Path";
+ case REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED:
+ return "Failed to load client certificate";
+ case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED:
+ return "Failed to load private key";
+ case REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED:
+ return "Failed to open system certificate store";
+ case REDIS_SSL_CTX_OS_CERT_ADD_FAILED:
+ return "Failed to add CA certificates obtained from system to the SSL context";
+ default:
+ return "Unknown error code";
+ }
+}
+
+void redisFreeSSLContext(redisSSLContext *ctx)
+{
+ if (!ctx)
+ return;
+
+ if (ctx->server_name) {
+ hi_free(ctx->server_name);
+ ctx->server_name = NULL;
+ }
+
+ if (ctx->ssl_ctx) {
+ SSL_CTX_free(ctx->ssl_ctx);
+ ctx->ssl_ctx = NULL;
+ }
+
+ hi_free(ctx);
+}
+
+
+/**
+ * redisSSLContext helper context initialization.
+ */
+
+redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
+ const char *cert_filename, const char *private_key_filename,
+ const char *server_name, redisSSLContextError *error)
+{
+ redisSSLOptions options = {
+ .cacert_filename = cacert_filename,
+ .capath = capath,
+ .cert_filename = cert_filename,
+ .private_key_filename = private_key_filename,
+ .server_name = server_name,
+ .verify_mode = REDIS_SSL_VERIFY_PEER,
+ };
+
+ return redisCreateSSLContextWithOptions(&options, error);
+}
+
+redisSSLContext *redisCreateSSLContextWithOptions(redisSSLOptions *options, redisSSLContextError *error) {
+ const char *cacert_filename = options->cacert_filename;
+ const char *capath = options->capath;
+ const char *cert_filename = options->cert_filename;
+ const char *private_key_filename = options->private_key_filename;
+ const char *server_name = options->server_name;
+
+#ifdef _WIN32
+ HCERTSTORE win_store = NULL;
+ PCCERT_CONTEXT win_ctx = NULL;
+#endif
+
+ redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext));
+ if (ctx == NULL)
+ goto error;
+
+ const SSL_METHOD *ssl_method;
+#if OPENSSL_VERSION_NUMBER >= OPENSSL_1_1_0
+ ssl_method = TLS_client_method();
+#else
+ ssl_method = SSLv23_client_method();
+#endif
+
+ ctx->ssl_ctx = SSL_CTX_new(ssl_method);
+ if (!ctx->ssl_ctx) {
+ if (error) *error = REDIS_SSL_CTX_CREATE_FAILED;
+ goto error;
+ }
+
+#if OPENSSL_VERSION_NUMBER >= OPENSSL_1_1_0
+ SSL_CTX_set_min_proto_version(ctx->ssl_ctx, TLS1_2_VERSION);
+#else
+ SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
+#endif
+
+ SSL_CTX_set_verify(ctx->ssl_ctx, options->verify_mode, NULL);
+
+ if ((cert_filename != NULL && private_key_filename == NULL) ||
+ (private_key_filename != NULL && cert_filename == NULL)) {
+ if (error) *error = REDIS_SSL_CTX_CERT_KEY_REQUIRED;
+ goto error;
+ }
+
+ if (capath || cacert_filename) {
+#ifdef _WIN32
+ if (0 == strcmp(cacert_filename, "wincert")) {
+ win_store = CertOpenSystemStore(NULL, "Root");
+ if (!win_store) {
+ if (error) *error = REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED;
+ goto error;
+ }
+ X509_STORE* store = SSL_CTX_get_cert_store(ctx->ssl_ctx);
+ while (win_ctx = CertEnumCertificatesInStore(win_store, win_ctx)) {
+ X509* x509 = NULL;
+ x509 = d2i_X509(NULL, (const unsigned char**)&win_ctx->pbCertEncoded, win_ctx->cbCertEncoded);
+ if (x509) {
+ if ((1 != X509_STORE_add_cert(store, x509)) ||
+ (1 != SSL_CTX_add_client_CA(ctx->ssl_ctx, x509)))
+ {
+ if (error) *error = REDIS_SSL_CTX_OS_CERT_ADD_FAILED;
+ goto error;
+ }
+ X509_free(x509);
+ }
+ }
+ CertFreeCertificateContext(win_ctx);
+ CertCloseStore(win_store, 0);
+ } else
+#endif
+ if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) {
+ if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED;
+ goto error;
+ }
+ } else {
+ if (!SSL_CTX_set_default_verify_paths(ctx->ssl_ctx)) {
+ if (error) *error = REDIS_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED;
+ goto error;
+ }
+ }
+
+ if (cert_filename) {
+ if (!SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, cert_filename)) {
+ if (error) *error = REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED;
+ goto error;
+ }
+ if (!SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, private_key_filename, SSL_FILETYPE_PEM)) {
+ if (error) *error = REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED;
+ goto error;
+ }
+ }
+
+ if (server_name)
+ ctx->server_name = hi_strdup(server_name);
+
+ return ctx;
+
+error:
+#ifdef _WIN32
+ CertFreeCertificateContext(win_ctx);
+ CertCloseStore(win_store, 0);
+#endif
+ redisFreeSSLContext(ctx);
+ return NULL;
+}
+
+/**
+ * SSL Connection initialization.
+ */
+
+
+static int redisSSLConnect(redisContext *c, SSL *ssl) {
+ if (c->privctx) {
+ __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
+ return REDIS_ERR;
+ }
+
+ redisSSL *rssl = hi_calloc(1, sizeof(redisSSL));
+ if (rssl == NULL) {
+ __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
+ return REDIS_ERR;
+ }
+
+ c->funcs = &redisContextSSLFuncs;
+ rssl->ssl = ssl;
+
+ SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+ SSL_set_fd(rssl->ssl, c->fd);
+ SSL_set_connect_state(rssl->ssl);
+
+ ERR_clear_error();
+ int rv = SSL_connect(rssl->ssl);
+ if (rv == 1) {
+ c->privctx = rssl;
+ return REDIS_OK;
+ }
+
+ rv = SSL_get_error(rssl->ssl, rv);
+ if (((c->flags & REDIS_BLOCK) == 0) &&
+ (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
+ c->privctx = rssl;
+ return REDIS_OK;
+ }
+
+ if (c->err == 0) {
+ char err[512];
+ if (rv == SSL_ERROR_SYSCALL)
+ snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
+ else {
+ unsigned long e = ERR_peek_last_error();
+ snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
+ ERR_reason_error_string(e));
+ }
+ __redisSetError(c, REDIS_ERR_IO, err);
+ }
+
+ hi_free(rssl);
+ return REDIS_ERR;
+}
+
+/**
+ * A wrapper around redisSSLConnect() for users who manage their own context and
+ * create their own SSL object.
+ */
+
+int redisInitiateSSL(redisContext *c, SSL *ssl) {
+ return redisSSLConnect(c, ssl);
+}
+
+/**
+ * A wrapper around redisSSLConnect() for users who use redisSSLContext and don't
+ * manage their own SSL objects.
+ */
+
+int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx)
+{
+ if (!c || !redis_ssl_ctx)
+ return REDIS_ERR;
+
+ /* We want to verify that redisSSLConnect() won't fail on this, as it will
+ * not own the SSL object in that case and we'll end up leaking.
+ */
+ if (c->privctx)
+ return REDIS_ERR;
+
+ SSL *ssl = SSL_new(redis_ssl_ctx->ssl_ctx);
+ if (!ssl) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
+ goto error;
+ }
+
+ if (redis_ssl_ctx->server_name) {
+ if (!SSL_set_tlsext_host_name(ssl, redis_ssl_ctx->server_name)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Failed to set server_name/SNI");
+ goto error;
+ }
+ }
+
+ if (redisSSLConnect(c, ssl) != REDIS_OK) {
+ goto error;
+ }
+
+ return REDIS_OK;
+
+error:
+ if (ssl)
+ SSL_free(ssl);
+ return REDIS_ERR;
+}
+
+static int maybeCheckWant(redisSSL *rssl, int rv) {
+ /**
+ * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
+ * and true is returned. False is returned otherwise
+ */
+ if (rv == SSL_ERROR_WANT_READ) {
+ rssl->wantRead = 1;
+ return 1;
+ } else if (rv == SSL_ERROR_WANT_WRITE) {
+ rssl->pendingWrite = 1;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/**
+ * Implementation of redisContextFuncs for SSL connections.
+ */
+
+static void redisSSLFree(void *privctx){
+ redisSSL *rsc = privctx;
+
+ if (!rsc) return;
+ if (rsc->ssl) {
+ SSL_free(rsc->ssl);
+ rsc->ssl = NULL;
+ }
+ hi_free(rsc);
+}
+
+static ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
+ redisSSL *rssl = c->privctx;
+
+ int nread = SSL_read(rssl->ssl, buf, bufcap);
+ if (nread > 0) {
+ return nread;
+ } else if (nread == 0) {
+ __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
+ return -1;
+ } else {
+ int err = SSL_get_error(rssl->ssl, nread);
+ if (c->flags & REDIS_BLOCK) {
+ /**
+ * In blocking mode, we should never end up in a situation where
+ * we get an error without it being an actual error, except
+ * in the case of EINTR, which can be spuriously received from
+ * debuggers or whatever.
+ */
+ if (errno == EINTR) {
+ return 0;
+ } else {
+ const char *msg = NULL;
+ if (errno == EAGAIN) {
+ msg = "Resource temporarily unavailable";
+ }
+ __redisSetError(c, REDIS_ERR_IO, msg);
+ return -1;
+ }
+ }
+
+ /**
+ * We can very well get an EWOULDBLOCK/EAGAIN, however
+ */
+ if (maybeCheckWant(rssl, err)) {
+ return 0;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+}
+
+static ssize_t redisSSLWrite(redisContext *c) {
+ redisSSL *rssl = c->privctx;
+
+ size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf);
+ int rv = SSL_write(rssl->ssl, c->obuf, len);
+
+ if (rv > 0) {
+ rssl->lastLen = 0;
+ } else if (rv < 0) {
+ rssl->lastLen = len;
+
+ int err = SSL_get_error(rssl->ssl, rv);
+ if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {
+ return 0;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+ return rv;
+}
+
+static void redisSSLAsyncRead(redisAsyncContext *ac) {
+ int rv;
+ redisSSL *rssl = ac->c.privctx;
+ redisContext *c = &ac->c;
+
+ rssl->wantRead = 0;
+
+ if (rssl->pendingWrite) {
+ int done;
+
+ /* This is probably just a write event */
+ rssl->pendingWrite = 0;
+ rv = redisBufferWrite(c, &done);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ return;
+ } else if (!done) {
+ _EL_ADD_WRITE(ac);
+ }
+ }
+
+ rv = redisBufferRead(c);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ } else {
+ _EL_ADD_READ(ac);
+ redisProcessCallbacks(ac);
+ }
+}
+
+static void redisSSLAsyncWrite(redisAsyncContext *ac) {
+ int rv, done = 0;
+ redisSSL *rssl = ac->c.privctx;
+ redisContext *c = &ac->c;
+
+ rssl->pendingWrite = 0;
+ rv = redisBufferWrite(c, &done);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ return;
+ }
+
+ if (!done) {
+ if (rssl->wantRead) {
+ /* Need to read-before-write */
+ rssl->pendingWrite = 1;
+ _EL_DEL_WRITE(ac);
+ } else {
+ /* No extra reads needed, just need to write more */
+ _EL_ADD_WRITE(ac);
+ }
+ } else {
+ /* Already done! */
+ _EL_DEL_WRITE(ac);
+ }
+
+ /* Always reschedule a read */
+ _EL_ADD_READ(ac);
+}
+
+redisContextFuncs redisContextSSLFuncs = {
+ .close = redisNetClose,
+ .free_privctx = redisSSLFree,
+ .async_read = redisSSLAsyncRead,
+ .async_write = redisSSLAsyncWrite,
+ .read = redisSSLRead,
+ .write = redisSSLWrite
+};
+
diff --git a/contrib/hiredis/test.c b/contrib/hiredis/test.c
new file mode 100644
index 000000000..dc7a78936
--- /dev/null
+++ b/contrib/hiredis/test.c
@@ -0,0 +1,2435 @@
+#include "fmacros.h"
+#include "sockcompat.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef _WIN32
+#include <strings.h>
+#include <sys/time.h>
+#endif
+#include <assert.h>
+#include <signal.h>
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+
+#include "hiredis.h"
+#include "async.h"
+#include "adapters/poll.h"
+#ifdef HIREDIS_TEST_SSL
+#include "hiredis_ssl.h"
+#endif
+#ifdef HIREDIS_TEST_ASYNC
+#include "adapters/libevent.h"
+#include <event2/event.h>
+#endif
+#include "net.h"
+#include "win32.h"
+
+enum connection_type {
+ CONN_TCP,
+ CONN_UNIX,
+ CONN_FD,
+ CONN_SSL
+};
+
+struct config {
+ enum connection_type type;
+ struct timeval connect_timeout;
+
+ struct {
+ const char *host;
+ int port;
+ } tcp;
+
+ struct {
+ const char *path;
+ } unix_sock;
+
+ struct {
+ const char *host;
+ int port;
+ const char *ca_cert;
+ const char *cert;
+ const char *key;
+ } ssl;
+};
+
+struct privdata {
+ int dtor_counter;
+};
+
+struct pushCounters {
+ int nil;
+ int str;
+};
+
+static int insecure_calloc_calls;
+
+#ifdef HIREDIS_TEST_SSL
+redisSSLContext *_ssl_ctx = NULL;
+#endif
+
+/* The following lines make up our testing "framework" :) */
+static int tests = 0, fails = 0, skips = 0;
+#define test(_s) { printf("#%02d ", ++tests); printf(_s); }
+#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;}
+#define test_skipped() { printf("\033[01;33mSKIPPED\033[0;0m\n"); skips++; }
+
+static void millisleep(int ms)
+{
+#ifdef _MSC_VER
+ Sleep(ms);
+#else
+ usleep(ms*1000);
+#endif
+}
+
+static long long usec(void) {
+#ifndef _MSC_VER
+ struct timeval tv;
+ gettimeofday(&tv,NULL);
+ return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
+#else
+ FILETIME ft;
+ GetSystemTimeAsFileTime(&ft);
+ return (((long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime) / 10;
+#endif
+}
+
+/* The assert() calls below have side effects, so we need assert()
+ * even if we are compiling without asserts (-DNDEBUG). */
+#ifdef NDEBUG
+#undef assert
+#define assert(e) (void)(e)
+#endif
+
+/* Helper to extract Redis version information. Aborts on any failure. */
+#define REDIS_VERSION_FIELD "redis_version:"
+void get_redis_version(redisContext *c, int *majorptr, int *minorptr) {
+ redisReply *reply;
+ char *eptr, *s, *e;
+ int major, minor;
+
+ reply = redisCommand(c, "INFO");
+ if (reply == NULL || c->err || reply->type != REDIS_REPLY_STRING)
+ goto abort;
+ if ((s = strstr(reply->str, REDIS_VERSION_FIELD)) == NULL)
+ goto abort;
+
+ s += strlen(REDIS_VERSION_FIELD);
+
+ /* We need a field terminator and at least 'x.y.z' (5) bytes of data */
+ if ((e = strstr(s, "\r\n")) == NULL || (e - s) < 5)
+ goto abort;
+
+ /* Extract version info */
+ major = strtol(s, &eptr, 10);
+ if (*eptr != '.') goto abort;
+ minor = strtol(eptr+1, NULL, 10);
+
+ /* Push info the caller wants */
+ if (majorptr) *majorptr = major;
+ if (minorptr) *minorptr = minor;
+
+ freeReplyObject(reply);
+ return;
+
+abort:
+ freeReplyObject(reply);
+ fprintf(stderr, "Error: Cannot determine Redis version, aborting\n");
+ exit(1);
+}
+
+static redisContext *select_database(redisContext *c) {
+ redisReply *reply;
+
+ /* Switch to DB 9 for testing, now that we know we can chat. */
+ reply = redisCommand(c,"SELECT 9");
+ assert(reply != NULL);
+ freeReplyObject(reply);
+
+ /* Make sure the DB is emtpy */
+ reply = redisCommand(c,"DBSIZE");
+ assert(reply != NULL);
+ if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) {
+ /* Awesome, DB 9 is empty and we can continue. */
+ freeReplyObject(reply);
+ } else {
+ printf("Database #9 is not empty, test can not continue\n");
+ exit(1);
+ }
+
+ return c;
+}
+
+/* Switch protocol */
+static void send_hello(redisContext *c, int version) {
+ redisReply *reply;
+ int expected;
+
+ reply = redisCommand(c, "HELLO %d", version);
+ expected = version == 3 ? REDIS_REPLY_MAP : REDIS_REPLY_ARRAY;
+ assert(reply != NULL && reply->type == expected);
+ freeReplyObject(reply);
+}
+
+/* Togggle client tracking */
+static void send_client_tracking(redisContext *c, const char *str) {
+ redisReply *reply;
+
+ reply = redisCommand(c, "CLIENT TRACKING %s", str);
+ assert(reply != NULL && reply->type == REDIS_REPLY_STATUS);
+ freeReplyObject(reply);
+}
+
+static int disconnect(redisContext *c, int keep_fd) {
+ redisReply *reply;
+
+ /* Make sure we're on DB 9. */
+ reply = redisCommand(c,"SELECT 9");
+ assert(reply != NULL);
+ freeReplyObject(reply);
+ reply = redisCommand(c,"FLUSHDB");
+ assert(reply != NULL);
+ freeReplyObject(reply);
+
+ /* Free the context as well, but keep the fd if requested. */
+ if (keep_fd)
+ return redisFreeKeepFd(c);
+ redisFree(c);
+ return -1;
+}
+
+static void do_ssl_handshake(redisContext *c) {
+#ifdef HIREDIS_TEST_SSL
+ redisInitiateSSLWithContext(c, _ssl_ctx);
+ if (c->err) {
+ printf("SSL error: %s\n", c->errstr);
+ redisFree(c);
+ exit(1);
+ }
+#else
+ (void) c;
+#endif
+}
+
+static redisContext *do_connect(struct config config) {
+ redisContext *c = NULL;
+
+ if (config.type == CONN_TCP) {
+ c = redisConnect(config.tcp.host, config.tcp.port);
+ } else if (config.type == CONN_SSL) {
+ c = redisConnect(config.ssl.host, config.ssl.port);
+ } else if (config.type == CONN_UNIX) {
+ c = redisConnectUnix(config.unix_sock.path);
+ } else if (config.type == CONN_FD) {
+ /* Create a dummy connection just to get an fd to inherit */
+ redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path);
+ if (dummy_ctx) {
+ int fd = disconnect(dummy_ctx, 1);
+ printf("Connecting to inherited fd %d\n", fd);
+ c = redisConnectFd(fd);
+ }
+ } else {
+ assert(NULL);
+ }
+
+ if (c == NULL) {
+ printf("Connection error: can't allocate redis context\n");
+ exit(1);
+ } else if (c->err) {
+ printf("Connection error: %s\n", c->errstr);
+ redisFree(c);
+ exit(1);
+ }
+
+ if (config.type == CONN_SSL) {
+ do_ssl_handshake(c);
+ }
+
+ return select_database(c);
+}
+
+static void do_reconnect(redisContext *c, struct config config) {
+ redisReconnect(c);
+
+ if (config.type == CONN_SSL) {
+ do_ssl_handshake(c);
+ }
+}
+
+static void test_format_commands(void) {
+ char *cmd;
+ int len;
+
+ test("Format command without interpolation: ");
+ len = redisFormatCommand(&cmd,"SET foo bar");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+ hi_free(cmd);
+
+ test("Format command with %%s string interpolation: ");
+ len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+ hi_free(cmd);
+
+ test("Format command with %%s and an empty string: ");
+ len = redisFormatCommand(&cmd,"SET %s %s","foo","");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(0+2));
+ hi_free(cmd);
+
+ test("Format command with an empty string in between proper interpolations: ");
+ len = redisFormatCommand(&cmd,"SET %s %s","","foo");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(0+2)+4+(3+2));
+ hi_free(cmd);
+
+ test("Format command with %%b string interpolation: ");
+ len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3);
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+ hi_free(cmd);
+
+ test("Format command with %%b and an empty string: ");
+ len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0);
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(0+2));
+ hi_free(cmd);
+
+ test("Format command with literal %%: ");
+ len = redisFormatCommand(&cmd,"SET %% %%");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(1+2)+4+(1+2));
+ hi_free(cmd);
+
+ /* Vararg width depends on the type. These tests make sure that the
+ * width is correctly determined using the format and subsequent varargs
+ * can correctly be interpolated. */
+#define INTEGER_WIDTH_TEST(fmt, type) do { \
+ type value = 123; \
+ test("Format command with printf-delegation (" #type "): "); \
+ len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \
+ test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \
+ len == 4+5+(12+2)+4+(9+2)); \
+ hi_free(cmd); \
+} while(0)
+
+#define FLOAT_WIDTH_TEST(type) do { \
+ type value = 123.0; \
+ test("Format command with printf-delegation (" #type "): "); \
+ len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \
+ test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \
+ len == 4+5+(12+2)+4+(9+2)); \
+ hi_free(cmd); \
+} while(0)
+
+ INTEGER_WIDTH_TEST("d", int);
+ INTEGER_WIDTH_TEST("hhd", char);
+ INTEGER_WIDTH_TEST("hd", short);
+ INTEGER_WIDTH_TEST("ld", long);
+ INTEGER_WIDTH_TEST("lld", long long);
+ INTEGER_WIDTH_TEST("u", unsigned int);
+ INTEGER_WIDTH_TEST("hhu", unsigned char);
+ INTEGER_WIDTH_TEST("hu", unsigned short);
+ INTEGER_WIDTH_TEST("lu", unsigned long);
+ INTEGER_WIDTH_TEST("llu", unsigned long long);
+ FLOAT_WIDTH_TEST(float);
+ FLOAT_WIDTH_TEST(double);
+
+ test("Format command with unhandled printf format (specifier 'p' not supported): ");
+ len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3);
+ test_cond(len == -1);
+
+ test("Format command with invalid printf format (specifier missing): ");
+ len = redisFormatCommand(&cmd,"%-");
+ test_cond(len == -1);
+
+ const char *argv[3];
+ argv[0] = "SET";
+ argv[1] = "foo\0xxx";
+ argv[2] = "bar";
+ size_t lens[3] = { 3, 7, 3 };
+ int argc = 3;
+
+ test("Format command by passing argc/argv without lengths: ");
+ len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+ hi_free(cmd);
+
+ test("Format command by passing argc/argv with lengths: ");
+ len = redisFormatCommandArgv(&cmd,argc,argv,lens);
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(7+2)+4+(3+2));
+ hi_free(cmd);
+
+ sds sds_cmd;
+
+ sds_cmd = NULL;
+ test("Format command into sds by passing argc/argv without lengths: ");
+ len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL);
+ test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+ sdsfree(sds_cmd);
+
+ sds_cmd = NULL;
+ test("Format command into sds by passing argc/argv with lengths: ");
+ len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens);
+ test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(7+2)+4+(3+2));
+ sdsfree(sds_cmd);
+}
+
+static void test_append_formatted_commands(struct config config) {
+ redisContext *c;
+ redisReply *reply;
+ char *cmd;
+ int len;
+
+ c = do_connect(config);
+
+ test("Append format command: ");
+
+ len = redisFormatCommand(&cmd, "SET foo bar");
+
+ test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK);
+
+ assert(redisGetReply(c, (void*)&reply) == REDIS_OK);
+
+ hi_free(cmd);
+ freeReplyObject(reply);
+
+ disconnect(c, 0);
+}
+
+static void test_tcp_options(struct config cfg) {
+ redisContext *c;
+
+ c = do_connect(cfg);
+
+ test("We can enable TCP_KEEPALIVE: ");
+ test_cond(redisEnableKeepAlive(c) == REDIS_OK);
+
+#ifdef TCP_USER_TIMEOUT
+ test("We can set TCP_USER_TIMEOUT: ");
+ test_cond(redisSetTcpUserTimeout(c, 100) == REDIS_OK);
+#else
+ test("Setting TCP_USER_TIMEOUT errors when unsupported: ");
+ test_cond(redisSetTcpUserTimeout(c, 100) == REDIS_ERR && c->err == REDIS_ERR_IO);
+#endif
+
+ redisFree(c);
+}
+
+static void test_reply_reader(void) {
+ redisReader *reader;
+ void *reply, *root;
+ int ret;
+ int i;
+
+ test("Error handling in reply parser: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader,(char*)"@foo\r\n",6);
+ ret = redisReaderGetReply(reader,NULL);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
+ redisReaderFree(reader);
+
+ /* when the reply already contains multiple items, they must be free'd
+ * on an error. valgrind will bark when this doesn't happen. */
+ test("Memory cleanup in reply parser: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader,(char*)"*2\r\n",4);
+ redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
+ redisReaderFeed(reader,(char*)"@foo\r\n",6);
+ ret = redisReaderGetReply(reader,NULL);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
+ redisReaderFree(reader);
+
+ reader = redisReaderCreate();
+ test("Can handle arbitrarily nested multi-bulks: ");
+ for (i = 0; i < 128; i++) {
+ redisReaderFeed(reader,(char*)"*1\r\n", 4);
+ }
+ redisReaderFeed(reader,(char*)"$6\r\nLOLWUT\r\n",12);
+ ret = redisReaderGetReply(reader,&reply);
+ root = reply; /* Keep track of the root reply */
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
+ ((redisReply*)reply)->elements == 1);
+
+ test("Can parse arbitrarily nested multi-bulks correctly: ");
+ while(i--) {
+ assert(reply != NULL && ((redisReply*)reply)->type == REDIS_REPLY_ARRAY);
+ reply = ((redisReply*)reply)->element[0];
+ }
+ test_cond(((redisReply*)reply)->type == REDIS_REPLY_STRING &&
+ !memcmp(((redisReply*)reply)->str, "LOLWUT", 6));
+ freeReplyObject(root);
+ redisReaderFree(reader);
+
+ test("Correctly parses LLONG_MAX: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ":9223372036854775807\r\n",22);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
+ ((redisReply*)reply)->integer == LLONG_MAX);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error when > LLONG_MAX: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ":9223372036854775808\r\n",22);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Bad integer value") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Correctly parses LLONG_MIN: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ":-9223372036854775808\r\n",23);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
+ ((redisReply*)reply)->integer == LLONG_MIN);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error when < LLONG_MIN: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ":-9223372036854775809\r\n",23);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Bad integer value") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error when array < -1: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "*-2\r\n+asdf\r\n",12);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error when bulk < -1: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "$-2\r\nasdf\r\n",11);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Can configure maximum multi-bulk elements: ");
+ reader = redisReaderCreate();
+ reader->maxelements = 1024;
+ redisReaderFeed(reader, "*1025\r\n", 7);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr, "Multi-bulk length out of range") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Multi-bulk never overflows regardless of maxelements: ");
+ size_t bad_mbulk_len = (SIZE_MAX / sizeof(void *)) + 3;
+ char bad_mbulk_reply[100];
+ snprintf(bad_mbulk_reply, sizeof(bad_mbulk_reply), "*%llu\r\n+asdf\r\n",
+ (unsigned long long) bad_mbulk_len);
+
+ reader = redisReaderCreate();
+ reader->maxelements = 0; /* Don't rely on default limit */
+ redisReaderFeed(reader, bad_mbulk_reply, strlen(bad_mbulk_reply));
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr, "Out of memory") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+#if LLONG_MAX > SIZE_MAX
+ test("Set error when array > SIZE_MAX: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error when bulk > SIZE_MAX: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+#endif
+
+ test("Works with NULL functions for reply: ");
+ reader = redisReaderCreate();
+ reader->fn = NULL;
+ redisReaderFeed(reader,(char*)"+OK\r\n",5);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
+ redisReaderFree(reader);
+
+ test("Works when a single newline (\\r\\n) covers two calls to feed: ");
+ reader = redisReaderCreate();
+ reader->fn = NULL;
+ redisReaderFeed(reader,(char*)"+OK\r",4);
+ ret = redisReaderGetReply(reader,&reply);
+ assert(ret == REDIS_OK && reply == NULL);
+ redisReaderFeed(reader,(char*)"\n",1);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
+ redisReaderFree(reader);
+
+ test("Don't reset state after protocol error: ");
+ reader = redisReaderCreate();
+ reader->fn = NULL;
+ redisReaderFeed(reader,(char*)"x",1);
+ ret = redisReaderGetReply(reader,&reply);
+ assert(ret == REDIS_ERR);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR && reply == NULL);
+ redisReaderFree(reader);
+
+ test("Don't reset state after protocol error(not segfault): ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader,(char*)"*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$", 25);
+ ret = redisReaderGetReply(reader,&reply);
+ assert(ret == REDIS_OK);
+ redisReaderFeed(reader,(char*)"3\r\nval\r\n", 8);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
+ ((redisReply*)reply)->elements == 3);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ /* Regression test for issue #45 on GitHub. */
+ test("Don't do empty allocation for empty multi bulk: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader,(char*)"*0\r\n",4);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
+ ((redisReply*)reply)->elements == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ /* RESP3 verbatim strings (GitHub issue #802) */
+ test("Can parse RESP3 verbatim strings: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader,(char*)"=10\r\ntxt:LOLWUT\r\n",17);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_VERB &&
+ !memcmp(((redisReply*)reply)->str,"LOLWUT", 6));
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ /* RESP3 push messages (Github issue #815) */
+ test("Can parse RESP3 push messages: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader,(char*)">2\r\n$6\r\nLOLWUT\r\n:42\r\n",21);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_PUSH &&
+ ((redisReply*)reply)->elements == 2 &&
+ ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STRING &&
+ !memcmp(((redisReply*)reply)->element[0]->str,"LOLWUT",6) &&
+ ((redisReply*)reply)->element[1]->type == REDIS_REPLY_INTEGER &&
+ ((redisReply*)reply)->element[1]->integer == 42);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Can parse RESP3 doubles: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ",3.14159265358979323846\r\n",25);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&
+ fabs(((redisReply*)reply)->dval - 3.14159265358979323846) < 0.00000001 &&
+ ((redisReply*)reply)->len == 22 &&
+ strcmp(((redisReply*)reply)->str, "3.14159265358979323846") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error on invalid RESP3 double: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ",3.14159\000265358979323846\r\n",26);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Bad double value") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Correctly parses RESP3 double INFINITY: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ",inf\r\n",6);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&
+ isinf(((redisReply*)reply)->dval) &&
+ ((redisReply*)reply)->dval > 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Correctly parses RESP3 double NaN: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ",nan\r\n",6);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&
+ isnan(((redisReply*)reply)->dval));
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Correctly parses RESP3 double -Nan: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ",-nan\r\n", 7);
+ ret = redisReaderGetReply(reader, &reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&
+ isnan(((redisReply*)reply)->dval));
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Can parse RESP3 nil: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "_\r\n",3);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_NIL);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error on invalid RESP3 nil: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "_nil\r\n",6);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Bad nil value") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Can parse RESP3 bool (true): ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "#t\r\n",4);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_BOOL &&
+ ((redisReply*)reply)->integer);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Can parse RESP3 bool (false): ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "#f\r\n",4);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_BOOL &&
+ !((redisReply*)reply)->integer);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error on invalid RESP3 bool: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "#foobar\r\n",9);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Bad bool value") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Can parse RESP3 map: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "%2\r\n+first\r\n:123\r\n$6\r\nsecond\r\n#t\r\n",34);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_MAP &&
+ ((redisReply*)reply)->elements == 4 &&
+ ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STATUS &&
+ ((redisReply*)reply)->element[0]->len == 5 &&
+ !strcmp(((redisReply*)reply)->element[0]->str,"first") &&
+ ((redisReply*)reply)->element[1]->type == REDIS_REPLY_INTEGER &&
+ ((redisReply*)reply)->element[1]->integer == 123 &&
+ ((redisReply*)reply)->element[2]->type == REDIS_REPLY_STRING &&
+ ((redisReply*)reply)->element[2]->len == 6 &&
+ !strcmp(((redisReply*)reply)->element[2]->str,"second") &&
+ ((redisReply*)reply)->element[3]->type == REDIS_REPLY_BOOL &&
+ ((redisReply*)reply)->element[3]->integer);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Can parse RESP3 set: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "~5\r\n+orange\r\n$5\r\napple\r\n#f\r\n:100\r\n:999\r\n",40);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_SET &&
+ ((redisReply*)reply)->elements == 5 &&
+ ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STATUS &&
+ ((redisReply*)reply)->element[0]->len == 6 &&
+ !strcmp(((redisReply*)reply)->element[0]->str,"orange") &&
+ ((redisReply*)reply)->element[1]->type == REDIS_REPLY_STRING &&
+ ((redisReply*)reply)->element[1]->len == 5 &&
+ !strcmp(((redisReply*)reply)->element[1]->str,"apple") &&
+ ((redisReply*)reply)->element[2]->type == REDIS_REPLY_BOOL &&
+ !((redisReply*)reply)->element[2]->integer &&
+ ((redisReply*)reply)->element[3]->type == REDIS_REPLY_INTEGER &&
+ ((redisReply*)reply)->element[3]->integer == 100 &&
+ ((redisReply*)reply)->element[4]->type == REDIS_REPLY_INTEGER &&
+ ((redisReply*)reply)->element[4]->integer == 999);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Can parse RESP3 bignum: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader,"(3492890328409238509324850943850943825024385\r\n",46);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_BIGNUM &&
+ ((redisReply*)reply)->len == 43 &&
+ !strcmp(((redisReply*)reply)->str,"3492890328409238509324850943850943825024385"));
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Can parse RESP3 doubles in an array: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "*1\r\n,3.14159265358979323846\r\n",31);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
+ ((redisReply*)reply)->elements == 1 &&
+ ((redisReply*)reply)->element[0]->type == REDIS_REPLY_DOUBLE &&
+ fabs(((redisReply*)reply)->element[0]->dval - 3.14159265358979323846) < 0.00000001 &&
+ ((redisReply*)reply)->element[0]->len == 22 &&
+ strcmp(((redisReply*)reply)->element[0]->str, "3.14159265358979323846") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+}
+
+static void test_free_null(void) {
+ void *redisCtx = NULL;
+ void *reply = NULL;
+
+ test("Don't fail when redisFree is passed a NULL value: ");
+ redisFree(redisCtx);
+ test_cond(redisCtx == NULL);
+
+ test("Don't fail when freeReplyObject is passed a NULL value: ");
+ freeReplyObject(reply);
+ test_cond(reply == NULL);
+}
+
+static void *hi_malloc_fail(size_t size) {
+ (void)size;
+ return NULL;
+}
+
+static void *hi_calloc_fail(size_t nmemb, size_t size) {
+ (void)nmemb;
+ (void)size;
+ return NULL;
+}
+
+static void *hi_calloc_insecure(size_t nmemb, size_t size) {
+ (void)nmemb;
+ (void)size;
+ insecure_calloc_calls++;
+ return (void*)0xdeadc0de;
+}
+
+static void *hi_realloc_fail(void *ptr, size_t size) {
+ (void)ptr;
+ (void)size;
+ return NULL;
+}
+
+static void test_allocator_injection(void) {
+ void *ptr;
+
+ hiredisAllocFuncs ha = {
+ .mallocFn = hi_malloc_fail,
+ .callocFn = hi_calloc_fail,
+ .reallocFn = hi_realloc_fail,
+ .strdupFn = strdup,
+ .freeFn = free,
+ };
+
+ // Override hiredis allocators
+ hiredisSetAllocators(&ha);
+
+ test("redisContext uses injected allocators: ");
+ redisContext *c = redisConnect("localhost", 6379);
+ test_cond(c == NULL);
+
+ test("redisReader uses injected allocators: ");
+ redisReader *reader = redisReaderCreate();
+ test_cond(reader == NULL);
+
+ /* Make sure hiredis itself protects against a non-overflow checking calloc */
+ test("hiredis calloc wrapper protects against overflow: ");
+ ha.callocFn = hi_calloc_insecure;
+ hiredisSetAllocators(&ha);
+ ptr = hi_calloc((SIZE_MAX / sizeof(void*)) + 3, sizeof(void*));
+ test_cond(ptr == NULL && insecure_calloc_calls == 0);
+
+ // Return allocators to default
+ hiredisResetAllocators();
+}
+
+#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
+static void test_blocking_connection_errors(void) {
+ struct addrinfo hints = {.ai_family = AF_INET};
+ struct addrinfo *ai_tmp = NULL;
+ redisContext *c;
+
+ int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp);
+ if (rv != 0) {
+ // Address does *not* exist
+ test("Returns error when host cannot be resolved: ");
+ // First see if this domain name *actually* resolves to NXDOMAIN
+ c = redisConnect(HIREDIS_BAD_DOMAIN, 6379);
+ test_cond(
+ c->err == REDIS_ERR_OTHER &&
+ (strcmp(c->errstr, "Name or service not known") == 0 ||
+ strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
+ strcmp(c->errstr, "Name does not resolve") == 0 ||
+ strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 ||
+ strcmp(c->errstr, "node name or service name not known") == 0 ||
+ strcmp(c->errstr, "No address associated with hostname") == 0 ||
+ strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
+ strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 ||
+ strcmp(c->errstr, "no address associated with name") == 0 ||
+ strcmp(c->errstr, "No such host is known. ") == 0));
+ redisFree(c);
+ } else {
+ printf("Skipping NXDOMAIN test. Found evil ISP!\n");
+ freeaddrinfo(ai_tmp);
+ }
+
+#ifndef _WIN32
+ redisOptions opt = {0};
+ struct timeval tv;
+
+ test("Returns error when the port is not open: ");
+ c = redisConnect((char*)"localhost", 1);
+ test_cond(c->err == REDIS_ERR_IO &&
+ strcmp(c->errstr,"Connection refused") == 0);
+ redisFree(c);
+
+
+ /* Verify we don't regress from the fix in PR #1180 */
+ test("We don't clobber connection exception with setsockopt error: ");
+ tv = (struct timeval){.tv_sec = 0, .tv_usec = 500000};
+ opt.command_timeout = opt.connect_timeout = &tv;
+ REDIS_OPTIONS_SET_TCP(&opt, "localhost", 10337);
+ c = redisConnectWithOptions(&opt);
+ test_cond(c->err == REDIS_ERR_IO &&
+ strcmp(c->errstr, "Connection refused") == 0);
+ redisFree(c);
+
+ test("Returns error when the unix_sock socket path doesn't accept connections: ");
+ c = redisConnectUnix((char*)"/tmp/idontexist.sock");
+ test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
+ redisFree(c);
+#endif
+}
+
+/* Test push handler */
+void push_handler(void *privdata, void *r) {
+ struct pushCounters *pcounts = privdata;
+ redisReply *reply = r, *payload;
+
+ assert(reply && reply->type == REDIS_REPLY_PUSH && reply->elements == 2);
+
+ payload = reply->element[1];
+ if (payload->type == REDIS_REPLY_ARRAY) {
+ payload = payload->element[0];
+ }
+
+ if (payload->type == REDIS_REPLY_STRING) {
+ pcounts->str++;
+ } else if (payload->type == REDIS_REPLY_NIL) {
+ pcounts->nil++;
+ }
+
+ freeReplyObject(reply);
+}
+
+/* Dummy function just to test setting a callback with redisOptions */
+void push_handler_async(redisAsyncContext *ac, void *reply) {
+ (void)ac;
+ (void)reply;
+}
+
+static void test_resp3_push_handler(redisContext *c) {
+ struct pushCounters pc = {0};
+ redisPushFn *old = NULL;
+ redisReply *reply;
+ void *privdata;
+
+ /* Switch to RESP3 and turn on client tracking */
+ send_hello(c, 3);
+ send_client_tracking(c, "ON");
+ privdata = c->privdata;
+ c->privdata = &pc;
+
+ reply = redisCommand(c, "GET key:0");
+ assert(reply != NULL);
+ freeReplyObject(reply);
+
+ test("RESP3 PUSH messages are handled out of band by default: ");
+ reply = redisCommand(c, "SET key:0 val:0");
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS);
+ freeReplyObject(reply);
+
+ assert((reply = redisCommand(c, "GET key:0")) != NULL);
+ freeReplyObject(reply);
+
+ old = redisSetPushCallback(c, push_handler);
+ test("We can set a custom RESP3 PUSH handler: ");
+ reply = redisCommand(c, "SET key:0 val:0");
+ /* We need another command because depending on the version of Redis, the
+ * notification may be delivered after the command's reply. */
+ assert(reply != NULL);
+ freeReplyObject(reply);
+ reply = redisCommand(c, "PING");
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.str == 1);
+ freeReplyObject(reply);
+
+ test("We properly handle a NIL invalidation payload: ");
+ reply = redisCommand(c, "FLUSHDB");
+ assert(reply != NULL);
+ freeReplyObject(reply);
+ reply = redisCommand(c, "PING");
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.nil == 1);
+ freeReplyObject(reply);
+
+ /* Unset the push callback and generate an invalidate message making
+ * sure it is not handled out of band. */
+ test("With no handler, PUSH replies come in-band: ");
+ redisSetPushCallback(c, NULL);
+ assert((reply = redisCommand(c, "GET key:0")) != NULL);
+ freeReplyObject(reply);
+ assert((reply = redisCommand(c, "SET key:0 invalid")) != NULL);
+ /* Depending on Redis version, we may receive either push notification or
+ * status reply. Both cases are valid. */
+ if (reply->type == REDIS_REPLY_STATUS) {
+ freeReplyObject(reply);
+ reply = redisCommand(c, "PING");
+ }
+ test_cond(reply->type == REDIS_REPLY_PUSH);
+ freeReplyObject(reply);
+
+ test("With no PUSH handler, no replies are lost: ");
+ assert(redisGetReply(c, (void**)&reply) == REDIS_OK);
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS);
+ freeReplyObject(reply);
+
+ /* Return to the originally set PUSH handler */
+ assert(old != NULL);
+ redisSetPushCallback(c, old);
+
+ /* Switch back to RESP2 and disable tracking */
+ c->privdata = privdata;
+ send_client_tracking(c, "OFF");
+ send_hello(c, 2);
+}
+
+redisOptions get_redis_tcp_options(struct config config) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port);
+ return options;
+}
+
+static void test_resp3_push_options(struct config config) {
+ redisAsyncContext *ac;
+ redisContext *c;
+ redisOptions options;
+
+ test("We set a default RESP3 handler for redisContext: ");
+ options = get_redis_tcp_options(config);
+ assert((c = redisConnectWithOptions(&options)) != NULL);
+ test_cond(c->push_cb != NULL);
+ redisFree(c);
+
+ test("We don't set a default RESP3 push handler for redisAsyncContext: ");
+ options = get_redis_tcp_options(config);
+ assert((ac = redisAsyncConnectWithOptions(&options)) != NULL);
+ test_cond(ac->c.push_cb == NULL);
+ redisAsyncFree(ac);
+
+ test("Our REDIS_OPT_NO_PUSH_AUTOFREE flag works: ");
+ options = get_redis_tcp_options(config);
+ options.options |= REDIS_OPT_NO_PUSH_AUTOFREE;
+ assert((c = redisConnectWithOptions(&options)) != NULL);
+ test_cond(c->push_cb == NULL);
+ redisFree(c);
+
+ test("We can use redisOptions to set a custom PUSH handler for redisContext: ");
+ options = get_redis_tcp_options(config);
+ options.push_cb = push_handler;
+ assert((c = redisConnectWithOptions(&options)) != NULL);
+ test_cond(c->push_cb == push_handler);
+ redisFree(c);
+
+ test("We can use redisOptions to set a custom PUSH handler for redisAsyncContext: ");
+ options = get_redis_tcp_options(config);
+ options.async_push_cb = push_handler_async;
+ assert((ac = redisAsyncConnectWithOptions(&options)) != NULL);
+ test_cond(ac->push_cb == push_handler_async);
+ redisAsyncFree(ac);
+}
+
+void free_privdata(void *privdata) {
+ struct privdata *data = privdata;
+ data->dtor_counter++;
+}
+
+static void test_privdata_hooks(struct config config) {
+ struct privdata data = {0};
+ redisOptions options;
+ redisContext *c;
+
+ test("We can use redisOptions to set privdata: ");
+ options = get_redis_tcp_options(config);
+ REDIS_OPTIONS_SET_PRIVDATA(&options, &data, free_privdata);
+ assert((c = redisConnectWithOptions(&options)) != NULL);
+ test_cond(c->privdata == &data);
+
+ test("Our privdata destructor fires when we free the context: ");
+ redisFree(c);
+ test_cond(data.dtor_counter == 1);
+}
+
+static void test_blocking_connection(struct config config) {
+ redisContext *c;
+ redisReply *reply;
+ int major;
+
+ c = do_connect(config);
+
+ test("Is able to deliver commands: ");
+ reply = redisCommand(c,"PING");
+ test_cond(reply->type == REDIS_REPLY_STATUS &&
+ strcasecmp(reply->str,"pong") == 0)
+ freeReplyObject(reply);
+
+ test("Is a able to send commands verbatim: ");
+ reply = redisCommand(c,"SET foo bar");
+ test_cond (reply->type == REDIS_REPLY_STATUS &&
+ strcasecmp(reply->str,"ok") == 0)
+ freeReplyObject(reply);
+
+ test("%%s String interpolation works: ");
+ reply = redisCommand(c,"SET %s %s","foo","hello world");
+ freeReplyObject(reply);
+ reply = redisCommand(c,"GET foo");
+ test_cond(reply->type == REDIS_REPLY_STRING &&
+ strcmp(reply->str,"hello world") == 0);
+ freeReplyObject(reply);
+
+ test("%%b String interpolation works: ");
+ reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11);
+ freeReplyObject(reply);
+ reply = redisCommand(c,"GET foo");
+ test_cond(reply->type == REDIS_REPLY_STRING &&
+ memcmp(reply->str,"hello\x00world",11) == 0)
+
+ test("Binary reply length is correct: ");
+ test_cond(reply->len == 11)
+ freeReplyObject(reply);
+
+ test("Can parse nil replies: ");
+ reply = redisCommand(c,"GET nokey");
+ test_cond(reply->type == REDIS_REPLY_NIL)
+ freeReplyObject(reply);
+
+ /* test 7 */
+ test("Can parse integer replies: ");
+ reply = redisCommand(c,"INCR mycounter");
+ test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)
+ freeReplyObject(reply);
+
+ test("Can parse multi bulk replies: ");
+ freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
+ freeReplyObject(redisCommand(c,"LPUSH mylist bar"));
+ reply = redisCommand(c,"LRANGE mylist 0 -1");
+ test_cond(reply->type == REDIS_REPLY_ARRAY &&
+ reply->elements == 2 &&
+ !memcmp(reply->element[0]->str,"bar",3) &&
+ !memcmp(reply->element[1]->str,"foo",3))
+ freeReplyObject(reply);
+
+ /* m/e with multi bulk reply *before* other reply.
+ * specifically test ordering of reply items to parse. */
+ test("Can handle nested multi bulk replies: ");
+ freeReplyObject(redisCommand(c,"MULTI"));
+ freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1"));
+ freeReplyObject(redisCommand(c,"PING"));
+ reply = (redisCommand(c,"EXEC"));
+ test_cond(reply->type == REDIS_REPLY_ARRAY &&
+ reply->elements == 2 &&
+ reply->element[0]->type == REDIS_REPLY_ARRAY &&
+ reply->element[0]->elements == 2 &&
+ !memcmp(reply->element[0]->element[0]->str,"bar",3) &&
+ !memcmp(reply->element[0]->element[1]->str,"foo",3) &&
+ reply->element[1]->type == REDIS_REPLY_STATUS &&
+ strcasecmp(reply->element[1]->str,"pong") == 0);
+ freeReplyObject(reply);
+
+ test("Send command by passing argc/argv: ");
+ const char *argv[3] = {"SET", "foo", "bar"};
+ size_t argvlen[3] = {3, 3, 3};
+ reply = redisCommandArgv(c,3,argv,argvlen);
+ test_cond(reply->type == REDIS_REPLY_STATUS);
+ freeReplyObject(reply);
+
+ /* Make sure passing NULL to redisGetReply is safe */
+ test("Can pass NULL to redisGetReply: ");
+ assert(redisAppendCommand(c, "PING") == REDIS_OK);
+ test_cond(redisGetReply(c, NULL) == REDIS_OK);
+
+ get_redis_version(c, &major, NULL);
+ if (major >= 6) test_resp3_push_handler(c);
+ test_resp3_push_options(config);
+
+ test_privdata_hooks(config);
+
+ disconnect(c, 0);
+}
+
+/* Send DEBUG SLEEP 0 to detect if we have this command */
+static int detect_debug_sleep(redisContext *c) {
+ int detected;
+ redisReply *reply = redisCommand(c, "DEBUG SLEEP 0\r\n");
+
+ if (reply == NULL || c->err) {
+ const char *cause = c->err ? c->errstr : "(none)";
+ fprintf(stderr, "Error testing for DEBUG SLEEP (Redis error: %s), exiting\n", cause);
+ exit(-1);
+ }
+
+ detected = reply->type == REDIS_REPLY_STATUS;
+ freeReplyObject(reply);
+
+ return detected;
+}
+
+static void test_blocking_connection_timeouts(struct config config) {
+ redisContext *c;
+ redisReply *reply;
+ ssize_t s;
+ const char *sleep_cmd = "DEBUG SLEEP 3\r\n";
+ struct timeval tv;
+
+ c = do_connect(config);
+ test("Successfully completes a command when the timeout is not exceeded: ");
+ reply = redisCommand(c,"SET foo fast");
+ freeReplyObject(reply);
+ tv.tv_sec = 0;
+ tv.tv_usec = 10000;
+ redisSetTimeout(c, tv);
+ reply = redisCommand(c, "GET foo");
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0);
+ freeReplyObject(reply);
+ disconnect(c, 0);
+
+ c = do_connect(config);
+ test("Does not return a reply when the command times out: ");
+ if (detect_debug_sleep(c)) {
+ redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd));
+
+ // flush connection buffer without waiting for the reply
+ s = c->funcs->write(c);
+ assert(s == (ssize_t)sdslen(c->obuf));
+ sdsfree(c->obuf);
+ c->obuf = sdsempty();
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 10000;
+ redisSetTimeout(c, tv);
+ reply = redisCommand(c, "GET foo");
+#ifndef _WIN32
+ test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO &&
+ strcmp(c->errstr, "Resource temporarily unavailable") == 0);
+#else
+ test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_TIMEOUT &&
+ strcmp(c->errstr, "recv timeout") == 0);
+#endif
+ freeReplyObject(reply);
+
+ // wait for the DEBUG SLEEP to complete so that Redis server is unblocked for the following tests
+ millisleep(3000);
+ } else {
+ test_skipped();
+ }
+
+ test("Reconnect properly reconnects after a timeout: ");
+ do_reconnect(c, config);
+ reply = redisCommand(c, "PING");
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
+ freeReplyObject(reply);
+
+ test("Reconnect properly uses owned parameters: ");
+ config.tcp.host = "foo";
+ config.unix_sock.path = "foo";
+ do_reconnect(c, config);
+ reply = redisCommand(c, "PING");
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
+ freeReplyObject(reply);
+
+ disconnect(c, 0);
+}
+
+static void test_blocking_io_errors(struct config config) {
+ redisContext *c;
+ redisReply *reply;
+ void *_reply;
+ int major, minor;
+
+ /* Connect to target given by config. */
+ c = do_connect(config);
+ get_redis_version(c, &major, &minor);
+
+ test("Returns I/O error when the connection is lost: ");
+ reply = redisCommand(c,"QUIT");
+ if (major > 2 || (major == 2 && minor > 0)) {
+ /* > 2.0 returns OK on QUIT and read() should be issued once more
+ * to know the descriptor is at EOF. */
+ test_cond(strcasecmp(reply->str,"OK") == 0 &&
+ redisGetReply(c,&_reply) == REDIS_ERR);
+ freeReplyObject(reply);
+ } else {
+ test_cond(reply == NULL);
+ }
+
+#ifndef _WIN32
+ /* On 2.0, QUIT will cause the connection to be closed immediately and
+ * the read(2) for the reply on QUIT will set the error to EOF.
+ * On >2.0, QUIT will return with OK and another read(2) needed to be
+ * issued to find out the socket was closed by the server. In both
+ * conditions, the error will be set to EOF. */
+ assert(c->err == REDIS_ERR_EOF &&
+ strcmp(c->errstr,"Server closed the connection") == 0);
+#endif
+ redisFree(c);
+
+ c = do_connect(config);
+ test("Returns I/O error on socket timeout: ");
+ struct timeval tv = { 0, 1000 };
+ assert(redisSetTimeout(c,tv) == REDIS_OK);
+ int respcode = redisGetReply(c,&_reply);
+#ifndef _WIN32
+ test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_IO && errno == EAGAIN);
+#else
+ test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_TIMEOUT);
+#endif
+ redisFree(c);
+}
+
+static void test_invalid_timeout_errors(struct config config) {
+ redisContext *c;
+
+ test("Set error when an invalid timeout usec value is used during connect: ");
+
+ config.connect_timeout.tv_sec = 0;
+ config.connect_timeout.tv_usec = 10000001;
+
+ if (config.type == CONN_TCP || config.type == CONN_SSL) {
+ c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout);
+ } else if(config.type == CONN_UNIX) {
+ c = redisConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout);
+ } else {
+ assert(NULL);
+ }
+
+ test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
+ redisFree(c);
+
+ test("Set error when an invalid timeout sec value is used during connect: ");
+
+ config.connect_timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1;
+ config.connect_timeout.tv_usec = 0;
+
+ if (config.type == CONN_TCP || config.type == CONN_SSL) {
+ c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout);
+ } else if(config.type == CONN_UNIX) {
+ c = redisConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout);
+ } else {
+ assert(NULL);
+ }
+
+ test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
+ redisFree(c);
+}
+
+/* Wrap malloc to abort on failure so OOM checks don't make the test logic
+ * harder to follow. */
+void *hi_malloc_safe(size_t size) {
+ void *ptr = hi_malloc(size);
+ if (ptr == NULL) {
+ fprintf(stderr, "Error: Out of memory\n");
+ exit(-1);
+ }
+
+ return ptr;
+}
+
+static void test_throughput(struct config config) {
+ redisContext *c = do_connect(config);
+ redisReply **replies;
+ int i, num;
+ long long t1, t2;
+
+ test("Throughput:\n");
+ for (i = 0; i < 500; i++)
+ freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
+
+ num = 1000;
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ replies[i] = redisCommand(c,"PING");
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ hi_free(replies);
+ printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ replies[i] = redisCommand(c,"LRANGE mylist 0 499");
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
+ assert(replies[i] != NULL && replies[i]->elements == 500);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ hi_free(replies);
+ printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000);
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ hi_free(replies);
+ printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ num = 10000;
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
+ for (i = 0; i < num; i++)
+ redisAppendCommand(c,"PING");
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ hi_free(replies);
+ printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
+ for (i = 0; i < num; i++)
+ redisAppendCommand(c,"LRANGE mylist 0 499");
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
+ assert(replies[i] != NULL && replies[i]->elements == 500);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ hi_free(replies);
+ printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
+ for (i = 0; i < num; i++)
+ redisAppendCommand(c,"INCRBY incrkey %d", 1000000);
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ hi_free(replies);
+ printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ disconnect(c, 0);
+}
+
+// static long __test_callback_flags = 0;
+// static void __test_callback(redisContext *c, void *privdata) {
+// ((void)c);
+// /* Shift to detect execution order */
+// __test_callback_flags <<= 8;
+// __test_callback_flags |= (long)privdata;
+// }
+//
+// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
+// ((void)c);
+// /* Shift to detect execution order */
+// __test_callback_flags <<= 8;
+// __test_callback_flags |= (long)privdata;
+// if (reply) freeReplyObject(reply);
+// }
+//
+// static redisContext *__connect_nonblock() {
+// /* Reset callback flags */
+// __test_callback_flags = 0;
+// return redisConnectNonBlock("127.0.0.1", port, NULL);
+// }
+//
+// static void test_nonblocking_connection() {
+// redisContext *c;
+// int wdone = 0;
+//
+// test("Calls command callback when command is issued: ");
+// c = __connect_nonblock();
+// redisSetCommandCallback(c,__test_callback,(void*)1);
+// redisCommand(c,"PING");
+// test_cond(__test_callback_flags == 1);
+// redisFree(c);
+//
+// test("Calls disconnect callback on redisDisconnect: ");
+// c = __connect_nonblock();
+// redisSetDisconnectCallback(c,__test_callback,(void*)2);
+// redisDisconnect(c);
+// test_cond(__test_callback_flags == 2);
+// redisFree(c);
+//
+// test("Calls disconnect callback and free callback on redisFree: ");
+// c = __connect_nonblock();
+// redisSetDisconnectCallback(c,__test_callback,(void*)2);
+// redisSetFreeCallback(c,__test_callback,(void*)4);
+// redisFree(c);
+// test_cond(__test_callback_flags == ((2 << 8) | 4));
+//
+// test("redisBufferWrite against empty write buffer: ");
+// c = __connect_nonblock();
+// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
+// redisFree(c);
+//
+// test("redisBufferWrite against not yet connected fd: ");
+// c = __connect_nonblock();
+// redisCommand(c,"PING");
+// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
+// strncmp(c->error,"write:",6) == 0);
+// redisFree(c);
+//
+// test("redisBufferWrite against closed fd: ");
+// c = __connect_nonblock();
+// redisCommand(c,"PING");
+// redisDisconnect(c);
+// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
+// strncmp(c->error,"write:",6) == 0);
+// redisFree(c);
+//
+// test("Process callbacks in the right sequence: ");
+// c = __connect_nonblock();
+// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
+// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
+// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
+//
+// /* Write output buffer */
+// wdone = 0;
+// while(!wdone) {
+// usleep(500);
+// redisBufferWrite(c,&wdone);
+// }
+//
+// /* Read until at least one callback is executed (the 3 replies will
+// * arrive in a single packet, causing all callbacks to be executed in
+// * a single pass). */
+// while(__test_callback_flags == 0) {
+// assert(redisBufferRead(c) == REDIS_OK);
+// redisProcessCallbacks(c);
+// }
+// test_cond(__test_callback_flags == 0x010203);
+// redisFree(c);
+//
+// test("redisDisconnect executes pending callbacks with NULL reply: ");
+// c = __connect_nonblock();
+// redisSetDisconnectCallback(c,__test_callback,(void*)1);
+// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
+// redisDisconnect(c);
+// test_cond(__test_callback_flags == 0x0201);
+// redisFree(c);
+// }
+
+#ifdef HIREDIS_TEST_ASYNC
+
+#pragma GCC diagnostic ignored "-Woverlength-strings" /* required on gcc 4.8.x due to assert statements */
+
+struct event_base *base;
+
+typedef struct TestState {
+ redisOptions *options;
+ int checkpoint;
+ int resp3;
+ int disconnect;
+} TestState;
+
+/* Helper to disconnect and stop event loop */
+void async_disconnect(redisAsyncContext *ac) {
+ redisAsyncDisconnect(ac);
+ event_base_loopbreak(base);
+}
+
+/* Testcase timeout, will trigger a failure */
+void timeout_cb(int fd, short event, void *arg) {
+ (void) fd; (void) event; (void) arg;
+ printf("Timeout in async testing!\n");
+ exit(1);
+}
+
+/* Unexpected call, will trigger a failure */
+void unexpected_cb(redisAsyncContext *ac, void *r, void *privdata) {
+ (void) ac; (void) r;
+ printf("Unexpected call: %s\n",(char*)privdata);
+ exit(1);
+}
+
+/* Helper function to publish a message via own client. */
+void publish_msg(redisOptions *options, const char* channel, const char* msg) {
+ redisContext *c = redisConnectWithOptions(options);
+ assert(c != NULL);
+ redisReply *reply = redisCommand(c,"PUBLISH %s %s",channel,msg);
+ assert(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1);
+ freeReplyObject(reply);
+ disconnect(c, 0);
+}
+
+/* Expect a reply of type INTEGER */
+void integer_cb(redisAsyncContext *ac, void *r, void *privdata) {
+ redisReply *reply = r;
+ TestState *state = privdata;
+ assert(reply != NULL && reply->type == REDIS_REPLY_INTEGER);
+ state->checkpoint++;
+ if (state->disconnect) async_disconnect(ac);
+}
+
+/* Subscribe callback for test_pubsub_handling and test_pubsub_handling_resp3:
+ * - a published message triggers an unsubscribe
+ * - a command is sent before the unsubscribe response is received. */
+void subscribe_cb(redisAsyncContext *ac, void *r, void *privdata) {
+ redisReply *reply = r;
+ TestState *state = privdata;
+
+ assert(reply != NULL &&
+ reply->type == (state->resp3 ? REDIS_REPLY_PUSH : REDIS_REPLY_ARRAY) &&
+ reply->elements == 3);
+
+ if (strcmp(reply->element[0]->str,"subscribe") == 0) {
+ assert(strcmp(reply->element[1]->str,"mychannel") == 0 &&
+ reply->element[2]->str == NULL);
+ publish_msg(state->options,"mychannel","Hello!");
+ } else if (strcmp(reply->element[0]->str,"message") == 0) {
+ assert(strcmp(reply->element[1]->str,"mychannel") == 0 &&
+ strcmp(reply->element[2]->str,"Hello!") == 0);
+ state->checkpoint++;
+
+ /* Unsubscribe after receiving the published message. Send unsubscribe
+ * which should call the callback registered during subscribe */
+ redisAsyncCommand(ac,unexpected_cb,
+ (void*)"unsubscribe should call subscribe_cb()",
+ "unsubscribe");
+ /* Send a regular command after unsubscribing, then disconnect */
+ state->disconnect = 1;
+ redisAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo");
+
+ } else if (strcmp(reply->element[0]->str,"unsubscribe") == 0) {
+ assert(strcmp(reply->element[1]->str,"mychannel") == 0 &&
+ reply->element[2]->str == NULL);
+ } else {
+ printf("Unexpected pubsub command: %s\n", reply->element[0]->str);
+ exit(1);
+ }
+}
+
+/* Expect a reply of type ARRAY */
+void array_cb(redisAsyncContext *ac, void *r, void *privdata) {
+ redisReply *reply = r;
+ TestState *state = privdata;
+ assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY);
+ state->checkpoint++;
+ if (state->disconnect) async_disconnect(ac);
+}
+
+/* Expect a NULL reply */
+void null_cb(redisAsyncContext *ac, void *r, void *privdata) {
+ (void) ac;
+ assert(r == NULL);
+ TestState *state = privdata;
+ state->checkpoint++;
+}
+
+static void test_pubsub_handling(struct config config) {
+ test("Subscribe, handle published message and unsubscribe: ");
+ /* Setup event dispatcher with a testcase timeout */
+ base = event_base_new();
+ struct event *timeout = evtimer_new(base, timeout_cb, NULL);
+ assert(timeout != NULL);
+
+ evtimer_assign(timeout,base,timeout_cb,NULL);
+ struct timeval timeout_tv = {.tv_sec = 10};
+ evtimer_add(timeout, &timeout_tv);
+
+ /* Connect */
+ redisOptions options = get_redis_tcp_options(config);
+ redisAsyncContext *ac = redisAsyncConnectWithOptions(&options);
+ assert(ac != NULL && ac->err == 0);
+ redisLibeventAttach(ac,base);
+
+ /* Start subscribe */
+ TestState state = {.options = &options};
+ redisAsyncCommand(ac,subscribe_cb,&state,"subscribe mychannel");
+
+ /* Make sure non-subscribe commands are handled */
+ redisAsyncCommand(ac,array_cb,&state,"PING");
+
+ /* Start event dispatching loop */
+ test_cond(event_base_dispatch(base) == 0);
+ event_free(timeout);
+ event_base_free(base);
+
+ /* Verify test checkpoints */
+ assert(state.checkpoint == 3);
+}
+
+/* Unexpected push message, will trigger a failure */
+void unexpected_push_cb(redisAsyncContext *ac, void *r) {
+ (void) ac; (void) r;
+ printf("Unexpected call to the PUSH callback!\n");
+ exit(1);
+}
+
+static void test_pubsub_handling_resp3(struct config config) {
+ test("Subscribe, handle published message and unsubscribe using RESP3: ");
+ /* Setup event dispatcher with a testcase timeout */
+ base = event_base_new();
+ struct event *timeout = evtimer_new(base, timeout_cb, NULL);
+ assert(timeout != NULL);
+
+ evtimer_assign(timeout,base,timeout_cb,NULL);
+ struct timeval timeout_tv = {.tv_sec = 10};
+ evtimer_add(timeout, &timeout_tv);
+
+ /* Connect */
+ redisOptions options = get_redis_tcp_options(config);
+ redisAsyncContext *ac = redisAsyncConnectWithOptions(&options);
+ assert(ac != NULL && ac->err == 0);
+ redisLibeventAttach(ac,base);
+
+ /* Not expecting any push messages in this test */
+ redisAsyncSetPushCallback(ac, unexpected_push_cb);
+
+ /* Switch protocol */
+ redisAsyncCommand(ac,NULL,NULL,"HELLO 3");
+
+ /* Start subscribe */
+ TestState state = {.options = &options, .resp3 = 1};
+ redisAsyncCommand(ac,subscribe_cb,&state,"subscribe mychannel");
+
+ /* Make sure non-subscribe commands are handled in RESP3 */
+ redisAsyncCommand(ac,integer_cb,&state,"LPUSH mylist foo");
+ redisAsyncCommand(ac,integer_cb,&state,"LPUSH mylist foo");
+ redisAsyncCommand(ac,integer_cb,&state,"LPUSH mylist foo");
+ /* Handle an array with 3 elements as a non-subscribe command */
+ redisAsyncCommand(ac,array_cb,&state,"LRANGE mylist 0 2");
+
+ /* Start event dispatching loop */
+ test_cond(event_base_dispatch(base) == 0);
+ event_free(timeout);
+ event_base_free(base);
+
+ /* Verify test checkpoints */
+ assert(state.checkpoint == 6);
+}
+
+/* Subscribe callback for test_command_timeout_during_pubsub:
+ * - a subscribe response triggers a published message
+ * - the published message triggers a command that times out
+ * - the command timeout triggers a disconnect */
+void subscribe_with_timeout_cb(redisAsyncContext *ac, void *r, void *privdata) {
+ redisReply *reply = r;
+ TestState *state = privdata;
+
+ /* The non-clean disconnect should trigger the
+ * subscription callback with a NULL reply. */
+ if (reply == NULL) {
+ state->checkpoint++;
+ event_base_loopbreak(base);
+ return;
+ }
+
+ assert(reply->type == (state->resp3 ? REDIS_REPLY_PUSH : REDIS_REPLY_ARRAY) &&
+ reply->elements == 3);
+
+ if (strcmp(reply->element[0]->str,"subscribe") == 0) {
+ assert(strcmp(reply->element[1]->str,"mychannel") == 0 &&
+ reply->element[2]->str == NULL);
+ publish_msg(state->options,"mychannel","Hello!");
+ state->checkpoint++;
+ } else if (strcmp(reply->element[0]->str,"message") == 0) {
+ assert(strcmp(reply->element[1]->str,"mychannel") == 0 &&
+ strcmp(reply->element[2]->str,"Hello!") == 0);
+ state->checkpoint++;
+
+ /* Send a command that will trigger a timeout */
+ redisAsyncCommand(ac,null_cb,state,"DEBUG SLEEP 3");
+ redisAsyncCommand(ac,null_cb,state,"LPUSH mylist foo");
+ } else {
+ printf("Unexpected pubsub command: %s\n", reply->element[0]->str);
+ exit(1);
+ }
+}
+
+static void test_command_timeout_during_pubsub(struct config config) {
+ test("Command timeout during Pub/Sub: ");
+ /* Setup event dispatcher with a testcase timeout */
+ base = event_base_new();
+ struct event *timeout = evtimer_new(base,timeout_cb,NULL);
+ assert(timeout != NULL);
+
+ evtimer_assign(timeout,base,timeout_cb,NULL);
+ struct timeval timeout_tv = {.tv_sec = 10};
+ evtimer_add(timeout,&timeout_tv);
+
+ /* Connect */
+ redisOptions options = get_redis_tcp_options(config);
+ redisAsyncContext *ac = redisAsyncConnectWithOptions(&options);
+ assert(ac != NULL && ac->err == 0);
+ redisLibeventAttach(ac,base);
+
+ /* Configure a command timout */
+ struct timeval command_timeout = {.tv_sec = 2};
+ redisAsyncSetTimeout(ac,command_timeout);
+
+ /* Not expecting any push messages in this test */
+ redisAsyncSetPushCallback(ac,unexpected_push_cb);
+
+ /* Switch protocol */
+ redisAsyncCommand(ac,NULL,NULL,"HELLO 3");
+
+ /* Start subscribe */
+ TestState state = {.options = &options, .resp3 = 1};
+ redisAsyncCommand(ac,subscribe_with_timeout_cb,&state,"subscribe mychannel");
+
+ /* Start event dispatching loop */
+ assert(event_base_dispatch(base) == 0);
+ event_free(timeout);
+ event_base_free(base);
+
+ /* Verify test checkpoints */
+ test_cond(state.checkpoint == 5);
+}
+
+/* Subscribe callback for test_pubsub_multiple_channels */
+void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) {
+ redisReply *reply = r;
+ TestState *state = privdata;
+
+ assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY &&
+ reply->elements == 3);
+
+ if (strcmp(reply->element[0]->str,"subscribe") == 0) {
+ assert(strcmp(reply->element[1]->str,"A") == 0);
+ publish_msg(state->options,"A","Hello!");
+ state->checkpoint++;
+ } else if (strcmp(reply->element[0]->str,"message") == 0) {
+ assert(strcmp(reply->element[1]->str,"A") == 0 &&
+ strcmp(reply->element[2]->str,"Hello!") == 0);
+ state->checkpoint++;
+
+ /* Unsubscribe to channels, including channel X & Z which we don't subscribe to */
+ redisAsyncCommand(ac,unexpected_cb,
+ (void*)"unsubscribe should not call unexpected_cb()",
+ "unsubscribe B X A A Z");
+ /* Unsubscribe to patterns, none which we subscribe to */
+ redisAsyncCommand(ac,unexpected_cb,
+ (void*)"punsubscribe should not call unexpected_cb()",
+ "punsubscribe");
+ /* Send a regular command after unsubscribing, then disconnect */
+ state->disconnect = 1;
+ redisAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo");
+ } else if (strcmp(reply->element[0]->str,"unsubscribe") == 0) {
+ assert(strcmp(reply->element[1]->str,"A") == 0);
+ state->checkpoint++;
+ } else {
+ printf("Unexpected pubsub command: %s\n", reply->element[0]->str);
+ exit(1);
+ }
+}
+
+/* Subscribe callback for test_pubsub_multiple_channels */
+void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) {
+ redisReply *reply = r;
+ TestState *state = privdata;
+ (void)ac;
+
+ assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY &&
+ reply->elements == 3);
+
+ if (strcmp(reply->element[0]->str,"subscribe") == 0) {
+ assert(strcmp(reply->element[1]->str,"B") == 0);
+ state->checkpoint++;
+ } else if (strcmp(reply->element[0]->str,"unsubscribe") == 0) {
+ assert(strcmp(reply->element[1]->str,"B") == 0);
+ state->checkpoint++;
+ } else {
+ printf("Unexpected pubsub command: %s\n", reply->element[0]->str);
+ exit(1);
+ }
+}
+
+/* Test handling of multiple channels
+ * - subscribe to channel A and B
+ * - a published message on A triggers an unsubscribe of channel B, X, A and Z
+ * where channel X and Z are not subscribed to.
+ * - the published message also triggers an unsubscribe to patterns. Since no
+ * pattern is subscribed to the responded pattern element type is NIL.
+ * - a command sent after unsubscribe triggers a disconnect */
+static void test_pubsub_multiple_channels(struct config config) {
+ test("Subscribe to multiple channels: ");
+ /* Setup event dispatcher with a testcase timeout */
+ base = event_base_new();
+ struct event *timeout = evtimer_new(base,timeout_cb,NULL);
+ assert(timeout != NULL);
+
+ evtimer_assign(timeout,base,timeout_cb,NULL);
+ struct timeval timeout_tv = {.tv_sec = 10};
+ evtimer_add(timeout,&timeout_tv);
+
+ /* Connect */
+ redisOptions options = get_redis_tcp_options(config);
+ redisAsyncContext *ac = redisAsyncConnectWithOptions(&options);
+ assert(ac != NULL && ac->err == 0);
+ redisLibeventAttach(ac,base);
+
+ /* Not expecting any push messages in this test */
+ redisAsyncSetPushCallback(ac,unexpected_push_cb);
+
+ /* Start subscribing to two channels */
+ TestState state = {.options = &options};
+ redisAsyncCommand(ac,subscribe_channel_a_cb,&state,"subscribe A");
+ redisAsyncCommand(ac,subscribe_channel_b_cb,&state,"subscribe B");
+
+ /* Start event dispatching loop */
+ assert(event_base_dispatch(base) == 0);
+ event_free(timeout);
+ event_base_free(base);
+
+ /* Verify test checkpoints */
+ test_cond(state.checkpoint == 6);
+}
+
+/* Command callback for test_monitor() */
+void monitor_cb(redisAsyncContext *ac, void *r, void *privdata) {
+ redisReply *reply = r;
+ TestState *state = privdata;
+
+ /* NULL reply is received when BYE triggers a disconnect. */
+ if (reply == NULL) {
+ event_base_loopbreak(base);
+ return;
+ }
+
+ assert(reply != NULL && reply->type == REDIS_REPLY_STATUS);
+ state->checkpoint++;
+
+ if (state->checkpoint == 1) {
+ /* Response from MONITOR */
+ redisContext *c = redisConnectWithOptions(state->options);
+ assert(c != NULL);
+ redisReply *reply = redisCommand(c,"SET first 1");
+ assert(reply->type == REDIS_REPLY_STATUS);
+ freeReplyObject(reply);
+ redisFree(c);
+ } else if (state->checkpoint == 2) {
+ /* Response for monitored command 'SET first 1' */
+ assert(strstr(reply->str,"first") != NULL);
+ redisContext *c = redisConnectWithOptions(state->options);
+ assert(c != NULL);
+ redisReply *reply = redisCommand(c,"SET second 2");
+ assert(reply->type == REDIS_REPLY_STATUS);
+ freeReplyObject(reply);
+ redisFree(c);
+ } else if (state->checkpoint == 3) {
+ /* Response for monitored command 'SET second 2' */
+ assert(strstr(reply->str,"second") != NULL);
+ /* Send QUIT to disconnect */
+ redisAsyncCommand(ac,NULL,NULL,"QUIT");
+ }
+}
+
+/* Test handling of the monitor command
+ * - sends MONITOR to enable monitoring.
+ * - sends SET commands via separate clients to be monitored.
+ * - sends QUIT to stop monitoring and disconnect. */
+static void test_monitor(struct config config) {
+ test("Enable monitoring: ");
+ /* Setup event dispatcher with a testcase timeout */
+ base = event_base_new();
+ struct event *timeout = evtimer_new(base, timeout_cb, NULL);
+ assert(timeout != NULL);
+
+ evtimer_assign(timeout,base,timeout_cb,NULL);
+ struct timeval timeout_tv = {.tv_sec = 10};
+ evtimer_add(timeout, &timeout_tv);
+
+ /* Connect */
+ redisOptions options = get_redis_tcp_options(config);
+ redisAsyncContext *ac = redisAsyncConnectWithOptions(&options);
+ assert(ac != NULL && ac->err == 0);
+ redisLibeventAttach(ac,base);
+
+ /* Not expecting any push messages in this test */
+ redisAsyncSetPushCallback(ac,unexpected_push_cb);
+
+ /* Start monitor */
+ TestState state = {.options = &options};
+ redisAsyncCommand(ac,monitor_cb,&state,"monitor");
+
+ /* Start event dispatching loop */
+ test_cond(event_base_dispatch(base) == 0);
+ event_free(timeout);
+ event_base_free(base);
+
+ /* Verify test checkpoints */
+ assert(state.checkpoint == 3);
+}
+#endif /* HIREDIS_TEST_ASYNC */
+
+/* tests for async api using polling adapter, requires no extra libraries*/
+
+/* enum for the test cases, the callbacks have different logic based on them */
+typedef enum astest_no
+{
+ ASTEST_CONNECT=0,
+ ASTEST_CONN_TIMEOUT,
+ ASTEST_PINGPONG,
+ ASTEST_PINGPONG_TIMEOUT,
+ ASTEST_ISSUE_931,
+ ASTEST_ISSUE_931_PING
+}astest_no;
+
+/* a static context for the async tests */
+struct _astest {
+ redisAsyncContext *ac;
+ astest_no testno;
+ int counter;
+ int connects;
+ int connect_status;
+ int disconnects;
+ int pongs;
+ int disconnect_status;
+ int connected;
+ int err;
+ char errstr[256];
+};
+static struct _astest astest;
+
+/* async callbacks */
+static void asCleanup(void* data)
+{
+ struct _astest *t = (struct _astest *)data;
+ t->ac = NULL;
+}
+
+static void commandCallback(struct redisAsyncContext *ac, void* _reply, void* _privdata);
+
+static void connectCallback(redisAsyncContext *c, int status) {
+ struct _astest *t = (struct _astest *)c->data;
+ assert(t == &astest);
+ assert(t->connects == 0);
+ t->err = c->err;
+ strcpy(t->errstr, c->errstr);
+ t->connects++;
+ t->connect_status = status;
+ t->connected = status == REDIS_OK ? 1 : -1;
+
+ if (t->testno == ASTEST_ISSUE_931) {
+ /* disconnect again */
+ redisAsyncDisconnect(c);
+ }
+ else if (t->testno == ASTEST_ISSUE_931_PING)
+ {
+ redisAsyncCommand(c, commandCallback, NULL, "PING");
+ }
+}
+static void disconnectCallback(const redisAsyncContext *c, int status) {
+ assert(c->data == (void*)&astest);
+ assert(astest.disconnects == 0);
+ astest.err = c->err;
+ strcpy(astest.errstr, c->errstr);
+ astest.disconnects++;
+ astest.disconnect_status = status;
+ astest.connected = 0;
+}
+
+static void commandCallback(struct redisAsyncContext *ac, void* _reply, void* _privdata)
+{
+ redisReply *reply = (redisReply*)_reply;
+ struct _astest *t = (struct _astest *)ac->data;
+ assert(t == &astest);
+ (void)_privdata;
+ t->err = ac->err;
+ strcpy(t->errstr, ac->errstr);
+ t->counter++;
+ if (t->testno == ASTEST_PINGPONG ||t->testno == ASTEST_ISSUE_931_PING)
+ {
+ assert(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
+ t->pongs++;
+ redisAsyncFree(ac);
+ }
+ if (t->testno == ASTEST_PINGPONG_TIMEOUT)
+ {
+ /* two ping pongs */
+ assert(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
+ t->pongs++;
+ if (t->counter == 1) {
+ int status = redisAsyncCommand(ac, commandCallback, NULL, "PING");
+ assert(status == REDIS_OK);
+ } else {
+ redisAsyncFree(ac);
+ }
+ }
+}
+
+static redisAsyncContext *do_aconnect(struct config config, astest_no testno)
+{
+ redisOptions options = {0};
+ memset(&astest, 0, sizeof(astest));
+
+ astest.testno = testno;
+ astest.connect_status = astest.disconnect_status = -2;
+
+ if (config.type == CONN_TCP) {
+ options.type = REDIS_CONN_TCP;
+ options.connect_timeout = &config.connect_timeout;
+ REDIS_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port);
+ } else if (config.type == CONN_SSL) {
+ options.type = REDIS_CONN_TCP;
+ options.connect_timeout = &config.connect_timeout;
+ REDIS_OPTIONS_SET_TCP(&options, config.ssl.host, config.ssl.port);
+ } else if (config.type == CONN_UNIX) {
+ options.type = REDIS_CONN_UNIX;
+ options.endpoint.unix_socket = config.unix_sock.path;
+ } else if (config.type == CONN_FD) {
+ options.type = REDIS_CONN_USERFD;
+ /* Create a dummy connection just to get an fd to inherit */
+ redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path);
+ if (dummy_ctx) {
+ redisFD fd = disconnect(dummy_ctx, 1);
+ printf("Connecting to inherited fd %d\n", (int)fd);
+ options.endpoint.fd = fd;
+ }
+ }
+ redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
+ assert(c);
+ astest.ac = c;
+ c->data = &astest;
+ c->dataCleanup = asCleanup;
+ redisPollAttach(c);
+ redisAsyncSetConnectCallbackNC(c, connectCallback);
+ redisAsyncSetDisconnectCallback(c, disconnectCallback);
+ return c;
+}
+
+static void as_printerr(void) {
+ printf("Async err %d : %s\n", astest.err, astest.errstr);
+}
+
+#define ASASSERT(e) do { \
+ if (!(e)) \
+ as_printerr(); \
+ assert(e); \
+} while (0);
+
+static void test_async_polling(struct config config) {
+ int status;
+ redisAsyncContext *c;
+ struct config defaultconfig = config;
+
+ test("Async connect: ");
+ c = do_aconnect(config, ASTEST_CONNECT);
+ assert(c);
+ while(astest.connected == 0)
+ redisPollTick(c, 0.1);
+ assert(astest.connects == 1);
+ ASASSERT(astest.connect_status == REDIS_OK);
+ assert(astest.disconnects == 0);
+ test_cond(astest.connected == 1);
+
+ test("Async free after connect: ");
+ assert(astest.ac != NULL);
+ redisAsyncFree(c);
+ assert(astest.disconnects == 1);
+ assert(astest.ac == NULL);
+ test_cond(astest.disconnect_status == REDIS_OK);
+
+ if (config.type == CONN_TCP || config.type == CONN_SSL) {
+ /* timeout can only be simulated with network */
+ test("Async connect timeout: ");
+ config.tcp.host = "192.168.254.254"; /* blackhole ip */
+ config.connect_timeout.tv_usec = 100000;
+ c = do_aconnect(config, ASTEST_CONN_TIMEOUT);
+ assert(c);
+ assert(c->err == 0);
+ while(astest.connected == 0)
+ redisPollTick(c, 0.1);
+ assert(astest.connected == -1);
+ /*
+ * freeing should not be done, clearing should have happened.
+ *redisAsyncFree(c);
+ */
+ assert(astest.ac == NULL);
+ test_cond(astest.connect_status == REDIS_ERR);
+ config = defaultconfig;
+ }
+
+ /* Test a ping/pong after connection */
+ test("Async PING/PONG: ");
+ c = do_aconnect(config, ASTEST_PINGPONG);
+ while(astest.connected == 0)
+ redisPollTick(c, 0.1);
+ status = redisAsyncCommand(c, commandCallback, NULL, "PING");
+ assert(status == REDIS_OK);
+ while(astest.ac)
+ redisPollTick(c, 0.1);
+ test_cond(astest.pongs == 1);
+
+ /* Test a ping/pong after connection that didn't time out.
+ * see https://github.com/redis/hiredis/issues/945
+ */
+ if (config.type == CONN_TCP || config.type == CONN_SSL) {
+ test("Async PING/PONG after connect timeout: ");
+ config.connect_timeout.tv_usec = 10000; /* 10ms */
+ c = do_aconnect(config, ASTEST_PINGPONG_TIMEOUT);
+ while(astest.connected == 0)
+ redisPollTick(c, 0.1);
+ /* sleep 0.1 s, allowing old timeout to arrive */
+ millisleep(10);
+ status = redisAsyncCommand(c, commandCallback, NULL, "PING");
+ assert(status == REDIS_OK);
+ while(astest.ac)
+ redisPollTick(c, 0.1);
+ test_cond(astest.pongs == 2);
+ config = defaultconfig;
+ }
+
+ /* Test disconnect from an on_connect callback
+ * see https://github.com/redis/hiredis/issues/931
+ */
+ test("Disconnect from onConnected callback (Issue #931): ");
+ c = do_aconnect(config, ASTEST_ISSUE_931);
+ while(astest.disconnects == 0)
+ redisPollTick(c, 0.1);
+ assert(astest.connected == 0);
+ assert(astest.connects == 1);
+ test_cond(astest.disconnects == 1);
+
+ /* Test ping/pong from an on_connect callback
+ * see https://github.com/redis/hiredis/issues/931
+ */
+ test("Ping/Pong from onConnected callback (Issue #931): ");
+ c = do_aconnect(config, ASTEST_ISSUE_931_PING);
+ /* connect callback issues ping, reponse callback destroys context */
+ while(astest.ac)
+ redisPollTick(c, 0.1);
+ assert(astest.connected == 0);
+ assert(astest.connects == 1);
+ assert(astest.disconnects == 1);
+ test_cond(astest.pongs == 1);
+}
+/* End of Async polling_adapter driven tests */
+
+int main(int argc, char **argv) {
+ struct config cfg = {
+ .tcp = {
+ .host = "127.0.0.1",
+ .port = 6379
+ },
+ .unix_sock = {
+ .path = "/tmp/redis.sock"
+ }
+ };
+ int throughput = 1;
+ int test_inherit_fd = 1;
+ int skips_as_fails = 0;
+ int test_unix_socket;
+
+ /* Parse command line options. */
+ argv++; argc--;
+ while (argc) {
+ if (argc >= 2 && !strcmp(argv[0],"-h")) {
+ argv++; argc--;
+ cfg.tcp.host = argv[0];
+ } else if (argc >= 2 && !strcmp(argv[0],"-p")) {
+ argv++; argc--;
+ cfg.tcp.port = atoi(argv[0]);
+ } else if (argc >= 2 && !strcmp(argv[0],"-s")) {
+ argv++; argc--;
+ cfg.unix_sock.path = argv[0];
+ } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) {
+ throughput = 0;
+ } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
+ test_inherit_fd = 0;
+ } else if (argc >= 1 && !strcmp(argv[0],"--skips-as-fails")) {
+ skips_as_fails = 1;
+#ifdef HIREDIS_TEST_SSL
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) {
+ argv++; argc--;
+ cfg.ssl.port = atoi(argv[0]);
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-host")) {
+ argv++; argc--;
+ cfg.ssl.host = argv[0];
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-ca-cert")) {
+ argv++; argc--;
+ cfg.ssl.ca_cert = argv[0];
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-cert")) {
+ argv++; argc--;
+ cfg.ssl.cert = argv[0];
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-key")) {
+ argv++; argc--;
+ cfg.ssl.key = argv[0];
+#endif
+ } else {
+ fprintf(stderr, "Invalid argument: %s\n", argv[0]);
+ exit(1);
+ }
+ argv++; argc--;
+ }
+
+#ifndef _WIN32
+ /* Ignore broken pipe signal (for I/O error tests). */
+ signal(SIGPIPE, SIG_IGN);
+
+ test_unix_socket = access(cfg.unix_sock.path, F_OK) == 0;
+
+#else
+ /* Unix sockets don't exist in Windows */
+ test_unix_socket = 0;
+#endif
+
+ test_allocator_injection();
+
+ test_format_commands();
+ test_reply_reader();
+ test_blocking_connection_errors();
+ test_free_null();
+
+ printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
+ cfg.type = CONN_TCP;
+ test_blocking_connection(cfg);
+ test_blocking_connection_timeouts(cfg);
+ test_blocking_io_errors(cfg);
+ test_invalid_timeout_errors(cfg);
+ test_append_formatted_commands(cfg);
+ test_tcp_options(cfg);
+ if (throughput) test_throughput(cfg);
+
+ printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path);
+ if (test_unix_socket) {
+ printf("\n");
+ cfg.type = CONN_UNIX;
+ test_blocking_connection(cfg);
+ test_blocking_connection_timeouts(cfg);
+ test_blocking_io_errors(cfg);
+ test_invalid_timeout_errors(cfg);
+ if (throughput) test_throughput(cfg);
+ } else {
+ test_skipped();
+ }
+
+#ifdef HIREDIS_TEST_SSL
+ if (cfg.ssl.port && cfg.ssl.host) {
+
+ redisInitOpenSSL();
+ _ssl_ctx = redisCreateSSLContext(cfg.ssl.ca_cert, NULL, cfg.ssl.cert, cfg.ssl.key, NULL, NULL);
+ assert(_ssl_ctx != NULL);
+
+ printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port);
+ cfg.type = CONN_SSL;
+
+ test_blocking_connection(cfg);
+ test_blocking_connection_timeouts(cfg);
+ test_blocking_io_errors(cfg);
+ test_invalid_timeout_errors(cfg);
+ test_append_formatted_commands(cfg);
+ if (throughput) test_throughput(cfg);
+
+ redisFreeSSLContext(_ssl_ctx);
+ _ssl_ctx = NULL;
+ }
+#endif
+
+#ifdef HIREDIS_TEST_ASYNC
+ cfg.type = CONN_TCP;
+ printf("\nTesting asynchronous API against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
+ cfg.type = CONN_TCP;
+
+ int major;
+ redisContext *c = do_connect(cfg);
+ get_redis_version(c, &major, NULL);
+ disconnect(c, 0);
+
+ test_pubsub_handling(cfg);
+ test_pubsub_multiple_channels(cfg);
+ test_monitor(cfg);
+ if (major >= 6) {
+ test_pubsub_handling_resp3(cfg);
+ test_command_timeout_during_pubsub(cfg);
+ }
+#endif /* HIREDIS_TEST_ASYNC */
+
+ cfg.type = CONN_TCP;
+ printf("\nTesting asynchronous API using polling_adapter TCP (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
+ test_async_polling(cfg);
+ if (test_unix_socket) {
+ cfg.type = CONN_UNIX;
+ printf("\nTesting asynchronous API using polling_adapter UNIX (%s):\n", cfg.unix_sock.path);
+ test_async_polling(cfg);
+ }
+
+ if (test_inherit_fd) {
+ printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path);
+ if (test_unix_socket) {
+ printf("\n");
+ cfg.type = CONN_FD;
+ test_blocking_connection(cfg);
+ } else {
+ test_skipped();
+ }
+ }
+
+ if (fails || (skips_as_fails && skips)) {
+ printf("*** %d TESTS FAILED ***\n", fails);
+ if (skips) {
+ printf("*** %d TESTS SKIPPED ***\n", skips);
+ }
+ return 1;
+ }
+
+ printf("ALL TESTS PASSED (%d skipped)\n", skips);
+ return 0;
+}
diff --git a/contrib/hiredis/win32.h b/contrib/hiredis/win32.h
new file mode 100644
index 000000000..04289c696
--- /dev/null
+++ b/contrib/hiredis/win32.h
@@ -0,0 +1,56 @@
+#ifndef _WIN32_HELPER_INCLUDE
+#define _WIN32_HELPER_INCLUDE
+#ifdef _MSC_VER
+
+#include <winsock2.h> /* for struct timeval */
+
+#ifndef inline
+#define inline __inline
+#endif
+
+#ifndef strcasecmp
+#define strcasecmp stricmp
+#endif
+
+#ifndef strncasecmp
+#define strncasecmp strnicmp
+#endif
+
+#ifndef va_copy
+#define va_copy(d,s) ((d) = (s))
+#endif
+
+#ifndef snprintf
+#define snprintf c99_snprintf
+
+__inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap)
+{
+ int count = -1;
+
+ if (size != 0)
+ count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);
+ if (count == -1)
+ count = _vscprintf(format, ap);
+
+ return count;
+}
+
+__inline int c99_snprintf(char* str, size_t size, const char* format, ...)
+{
+ int count;
+ va_list ap;
+
+ va_start(ap, format);
+ count = c99_vsnprintf(str, size, format, ap);
+ va_end(ap);
+
+ return count;
+}
+#endif
+#endif /* _MSC_VER */
+
+#ifdef _WIN32
+#define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
+#endif /* _WIN32 */
+
+#endif /* _WIN32_HELPER_INCLUDE */
diff --git a/contrib/publicsuffix/effective_tld_names.dat b/contrib/publicsuffix/effective_tld_names.dat
index bbb7ec699..6e17a262e 100644
--- a/contrib/publicsuffix/effective_tld_names.dat
+++ b/contrib/publicsuffix/effective_tld_names.dat
@@ -18,7 +18,7 @@ net.ac
mil.ac
org.ac
-// ad : https://en.wikipedia.org/wiki/.ad
+// ad : https://www.iana.org/domains/root/db/ad.html
ad
nom.ad
@@ -172,7 +172,7 @@ commune.am
net.am
org.am
-// ao : https://en.wikipedia.org/wiki/.ao
+// ao : https://www.iana.org/domains/root/db/ao.html
// http://www.dns.ao/REGISTR.DOC
ao
ed.ao
@@ -182,7 +182,7 @@ co.ao
pb.ao
it.ao
-// aq : https://en.wikipedia.org/wiki/.aq
+// aq : https://www.iana.org/domains/root/db/aq.html
aq
// ar : https://nic.ar/es/nic-argentina/normativa
@@ -202,7 +202,7 @@ org.ar
senasa.ar
tur.ar
-// arpa : https://en.wikipedia.org/wiki/.arpa
+// arpa : https://www.iana.org/domains/root/db/arpa.html
// Confirmed by registry <iana-questions@icann.org> 2008-06-18
arpa
e164.arpa
@@ -212,14 +212,14 @@ iris.arpa
uri.arpa
urn.arpa
-// as : https://en.wikipedia.org/wiki/.as
+// as : https://www.iana.org/domains/root/db/as.html
as
gov.as
-// asia : https://en.wikipedia.org/wiki/.asia
+// asia : https://www.iana.org/domains/root/db/asia.html
asia
-// at : https://en.wikipedia.org/wiki/.at
+// at : https://www.iana.org/domains/root/db/at.html
// Confirmed by registry <it@nic.at> 2008-06-17
at
ac.at
@@ -228,7 +228,7 @@ gv.at
or.at
sth.ac.at
-// au : https://en.wikipedia.org/wiki/.au
+// au : https://www.iana.org/domains/root/db/au.html
// http://www.auda.org.au/
au
// 2LDs
@@ -275,14 +275,14 @@ wa.gov.au
// education.tas.edu.au - Removed at the request of the Department of Education Tasmania
schools.nsw.edu.au
-// aw : https://en.wikipedia.org/wiki/.aw
+// aw : https://www.iana.org/domains/root/db/aw.html
aw
com.aw
-// ax : https://en.wikipedia.org/wiki/.ax
+// ax : https://www.iana.org/domains/root/db/ax.html
ax
-// az : https://en.wikipedia.org/wiki/.az
+// az : https://www.iana.org/domains/root/db/az.html
az
com.az
net.az
@@ -306,7 +306,7 @@ mil.ba
net.ba
org.ba
-// bb : https://en.wikipedia.org/wiki/.bb
+// bb : https://www.iana.org/domains/root/db/bb.html
bb
biz.bb
co.bb
@@ -319,19 +319,19 @@ org.bb
store.bb
tv.bb
-// bd : https://en.wikipedia.org/wiki/.bd
+// bd : https://www.iana.org/domains/root/db/bd.html
*.bd
-// be : https://en.wikipedia.org/wiki/.be
+// be : https://www.iana.org/domains/root/db/be.html
// Confirmed by registry <tech@dns.be> 2008-06-08
be
ac.be
-// bf : https://en.wikipedia.org/wiki/.bf
+// bf : https://www.iana.org/domains/root/db/bf.html
bf
gov.bf
-// bg : https://en.wikipedia.org/wiki/.bg
+// bg : https://www.iana.org/domains/root/db/bg.html
// https://www.register.bg/user/static/rules/en/index.html
bg
a.bg
@@ -371,7 +371,7 @@ z.bg
8.bg
9.bg
-// bh : https://en.wikipedia.org/wiki/.bh
+// bh : https://www.iana.org/domains/root/db/bh.html
bh
com.bh
edu.bh
@@ -379,7 +379,7 @@ net.bh
org.bh
gov.bh
-// bi : https://en.wikipedia.org/wiki/.bi
+// bi : https://www.iana.org/domains/root/db/bi.html
// http://whois.nic.bi/
bi
co.bi
@@ -388,7 +388,7 @@ edu.bi
or.bi
org.bi
-// biz : https://en.wikipedia.org/wiki/.biz
+// biz : https://www.iana.org/domains/root/db/biz.html
biz
// bj : https://nic.bj/bj-suffixes.txt
@@ -495,6 +495,7 @@ ato.br
b.br
barueri.br
belem.br
+bet.br
bhz.br
bib.br
bio.br
@@ -582,6 +583,7 @@ joinville.br
jor.br
jus.br
leg.br
+leilao.br
lel.br
log.br
londrina.br
@@ -657,7 +659,7 @@ org.bs
edu.bs
gov.bs
-// bt : https://en.wikipedia.org/wiki/.bt
+// bt : https://www.iana.org/domains/root/db/bt.html
bt
com.bt
edu.bt
@@ -669,14 +671,14 @@ org.bt
// Submitted by registry <jarle@uninett.no>
bv
-// bw : https://en.wikipedia.org/wiki/.bw
+// bw : https://www.iana.org/domains/root/db/bw.html
// http://www.gobin.info/domainname/bw.doc
// list of other 2nd level tlds ?
bw
co.bw
org.bw
-// by : https://en.wikipedia.org/wiki/.by
+// by : https://www.iana.org/domains/root/db/by.html
// http://tld.by/rules_2006_en.html
// list of other 2nd level tlds ?
by
@@ -689,7 +691,7 @@ com.by
// http://hoster.by/
of.by
-// bz : https://en.wikipedia.org/wiki/.bz
+// bz : https://www.iana.org/domains/root/db/bz.html
// http://www.belizenic.bz/
bz
com.bz
@@ -698,7 +700,7 @@ org.bz
edu.bz
gov.bz
-// ca : https://en.wikipedia.org/wiki/.ca
+// ca : https://www.iana.org/domains/root/db/ca.html
ca
// ca geographical names
ab.ca
@@ -719,27 +721,27 @@ yk.ca
// see also: http://registry.gc.ca/en/SubdomainFAQ
gc.ca
-// cat : https://en.wikipedia.org/wiki/.cat
+// cat : https://www.iana.org/domains/root/db/cat.html
cat
-// cc : https://en.wikipedia.org/wiki/.cc
+// cc : https://www.iana.org/domains/root/db/cc.html
cc
-// cd : https://en.wikipedia.org/wiki/.cd
+// cd : https://www.iana.org/domains/root/db/cd.html
// see also: https://www.nic.cd/domain/insertDomain_2.jsp?act=1
cd
gov.cd
-// cf : https://en.wikipedia.org/wiki/.cf
+// cf : https://www.iana.org/domains/root/db/cf.html
cf
-// cg : https://en.wikipedia.org/wiki/.cg
+// cg : https://www.iana.org/domains/root/db/cg.html
cg
-// ch : https://en.wikipedia.org/wiki/.ch
+// ch : https://www.iana.org/domains/root/db/ch.html
ch
-// ci : https://en.wikipedia.org/wiki/.ci
+// ci : https://www.iana.org/domains/root/db/ci.html
// http://www.nic.ci/index.php?page=charte
ci
org.ci
@@ -759,7 +761,7 @@ presse.ci
md.ci
gouv.ci
-// ck : https://en.wikipedia.org/wiki/.ck
+// ck : https://www.iana.org/domains/root/db/ck.html
*.ck
!www.ck
@@ -771,14 +773,14 @@ gob.cl
gov.cl
mil.cl
-// cm : https://en.wikipedia.org/wiki/.cm plus bug 981927
+// cm : https://www.iana.org/domains/root/db/cm.html plus bug 981927
cm
co.cm
com.cm
gov.cm
net.cm
-// cn : https://en.wikipedia.org/wiki/.cn
+// cn : https://www.iana.org/domains/root/db/cn.html
// Submitted by registry <tanyaling@cnnic.cn>
cn
ac.cn
@@ -830,7 +832,7 @@ hk.cn
mo.cn
tw.cn
-// co : https://en.wikipedia.org/wiki/.co
+// co : https://www.iana.org/domains/root/db/co.html
// Submitted by registry <tecnico@uniandes.edu.co>
co
arts.co
@@ -847,10 +849,10 @@ org.co
rec.co
web.co
-// com : https://en.wikipedia.org/wiki/.com
+// com : https://www.iana.org/domains/root/db/com.html
com
-// coop : https://en.wikipedia.org/wiki/.coop
+// coop : https://www.iana.org/domains/root/db/coop.html
coop
// cr : http://www.nic.cr/niccr_publico/showRegistroDominiosScreen.do
@@ -863,16 +865,18 @@ go.cr
or.cr
sa.cr
-// cu : https://en.wikipedia.org/wiki/.cu
+// cu : https://www.iana.org/domains/root/db/cu.html
cu
com.cu
edu.cu
-org.cu
-net.cu
+gob.cu
gov.cu
inf.cu
+nat.cu
+net.cu
+org.cu
-// cv : https://en.wikipedia.org/wiki/.cv
+// cv : https://www.iana.org/domains/root/db/cv.html
// cv : http://www.dns.cv/tldcv_portal/do?com=DS;5446457100;111;+PAGE(4000018)+K-CAT-CODIGO(RDOM)+RCNT(100); <- registration rules
cv
com.cv
@@ -889,7 +893,7 @@ edu.cw
net.cw
org.cw
-// cx : https://en.wikipedia.org/wiki/.cx
+// cx : https://www.iana.org/domains/root/db/cx.html
// list of other 2nd level tlds ?
cx
gov.cx
@@ -911,22 +915,22 @@ press.cy
pro.cy
tm.cy
-// cz : https://en.wikipedia.org/wiki/.cz
+// cz : https://www.iana.org/domains/root/db/cz.html
cz
-// de : https://en.wikipedia.org/wiki/.de
+// de : https://www.iana.org/domains/root/db/de.html
// Confirmed by registry <ops@denic.de> (with technical
// reservations) 2008-07-01
de
-// dj : https://en.wikipedia.org/wiki/.dj
+// dj : https://www.iana.org/domains/root/db/dj.html
dj
-// dk : https://en.wikipedia.org/wiki/.dk
+// dk : https://www.iana.org/domains/root/db/dk.html
// Confirmed by registry <robert@dk-hostmaster.dk> 2008-06-17
dk
-// dm : https://en.wikipedia.org/wiki/.dm
+// dm : https://www.iana.org/domains/root/db/dm.html
dm
com.dm
net.dm
@@ -934,7 +938,7 @@ org.dm
edu.dm
gov.dm
-// do : https://en.wikipedia.org/wiki/.do
+// do : https://www.iana.org/domains/root/db/do.html
do
art.do
com.do
@@ -976,7 +980,7 @@ gov.ec
gob.ec
mil.ec
-// edu : https://en.wikipedia.org/wiki/.edu
+// edu : https://www.iana.org/domains/root/db/edu.html
edu
// ee : http://www.eenet.ee/EENet/dom_reeglid.html#lisa_B
@@ -992,7 +996,7 @@ aip.ee
org.ee
fie.ee
-// eg : https://en.wikipedia.org/wiki/.eg
+// eg : https://www.iana.org/domains/root/db/eg.html
eg
com.eg
edu.eg
@@ -1004,7 +1008,7 @@ net.eg
org.eg
sci.eg
-// er : https://en.wikipedia.org/wiki/.er
+// er : https://www.iana.org/domains/root/db/er.html
*.er
// es : https://www.nic.es/site_ingles/ingles/dominios/index.html
@@ -1015,7 +1019,7 @@ org.es
gob.es
edu.es
-// et : https://en.wikipedia.org/wiki/.et
+// et : https://www.iana.org/domains/root/db/et.html
et
com.et
gov.et
@@ -1026,7 +1030,7 @@ name.et
info.et
net.et
-// eu : https://en.wikipedia.org/wiki/.eu
+// eu : https://www.iana.org/domains/root/db/eu.html
eu
// fi : https://www.iana.org/domains/root/db/fi.html
@@ -1051,17 +1055,17 @@ net.fj
org.fj
pro.fj
-// fk : https://en.wikipedia.org/wiki/.fk
+// fk : https://www.iana.org/domains/root/db/fk.html
*.fk
-// fm : https://en.wikipedia.org/wiki/.fm
+// fm : https://www.iana.org/domains/root/db/fm.html
com.fm
edu.fm
net.fm
org.fm
fm
-// fo : https://en.wikipedia.org/wiki/.fo
+// fo : https://www.iana.org/domains/root/db/fo.html
fo
// fr : https://www.afnic.fr/ https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf
@@ -1078,14 +1082,14 @@ cci.fr
greta.fr
huissier-justice.fr
-// ga : https://en.wikipedia.org/wiki/.ga
+// ga : https://www.iana.org/domains/root/db/ga.html
ga
// gb : This registry is effectively dormant
// Submitted by registry <Damien.Shaw@ja.net>
gb
-// gd : https://en.wikipedia.org/wiki/.gd
+// gd : https://www.iana.org/domains/root/db/gd.html
edu.gd
gov.gd
gd
@@ -1100,7 +1104,7 @@ mil.ge
net.ge
pvt.ge
-// gf : https://en.wikipedia.org/wiki/.gf
+// gf : https://www.iana.org/domains/root/db/gf.html
gf
// gg : http://www.channelisles.net/register-domains/
@@ -1110,7 +1114,7 @@ co.gg
net.gg
org.gg
-// gh : https://en.wikipedia.org/wiki/.gh
+// gh : https://www.iana.org/domains/root/db/gh.html
// see also: http://www.nic.gh/reg_now.php
// Although domains directly at second level are not possible at the moment,
// they have been possible for some time and may come back.
@@ -1130,7 +1134,7 @@ mod.gi
edu.gi
org.gi
-// gl : https://en.wikipedia.org/wiki/.gl
+// gl : https://www.iana.org/domains/root/db/gl.html
// http://nic.gl
gl
co.gl
@@ -1152,7 +1156,7 @@ gov.gn
org.gn
net.gn
-// gov : https://en.wikipedia.org/wiki/.gov
+// gov : https://www.iana.org/domains/root/db/gov.html
gov
// gp : http://www.nic.gp/index.php?lang=en
@@ -1164,7 +1168,7 @@ edu.gp
org.gp
asso.gp
-// gq : https://en.wikipedia.org/wiki/.gq
+// gq : https://www.iana.org/domains/root/db/gq.html
gq
// gr : https://grweb.ics.forth.gr/english/1617-B-2005.html
@@ -1176,7 +1180,7 @@ net.gr
org.gr
gov.gr
-// gs : https://en.wikipedia.org/wiki/.gs
+// gs : https://www.iana.org/domains/root/db/gs.html
gs
// gt : https://www.gt/sitio/registration_policy.php?lang=en
@@ -1202,11 +1206,11 @@ net.gu
org.gu
web.gu
-// gw : https://en.wikipedia.org/wiki/.gw
+// gw : https://www.iana.org/domains/root/db/gw.html
// gw : https://nic.gw/regras/
gw
-// gy : https://en.wikipedia.org/wiki/.gy
+// gy : https://www.iana.org/domains/root/db/gy.html
// http://registry.gy/
gy
co.gy
@@ -1256,7 +1260,7 @@ xn--uc0atv.hk
xn--uc0ay4a.hk
組织.hk
-// hm : https://en.wikipedia.org/wiki/.hm
+// hm : https://www.iana.org/domains/root/db/hm.html
hm
// hn : http://www.nic.hn/politicas/ps02,,05.html
@@ -1345,7 +1349,7 @@ ponpes.id
sch.id
web.id
-// ie : https://en.wikipedia.org/wiki/.ie
+// ie : https://www.iana.org/domains/root/db/ie.html
ie
gov.ie
@@ -1390,7 +1394,7 @@ plc.co.im
tt.im
tv.im
-// in : https://en.wikipedia.org/wiki/.in
+// in : https://www.iana.org/domains/root/db/in.html
// see also: https://registry.in/policies
// Please note, that nic.in is not an official eTLD, but used by most
// government institutions.
@@ -1437,10 +1441,10 @@ uk.in
up.in
us.in
-// info : https://en.wikipedia.org/wiki/.info
+// info : https://www.iana.org/domains/root/db/info.html
info
-// int : https://en.wikipedia.org/wiki/.int
+// int : https://www.iana.org/domains/root/db/int.html
// Confirmed by registry <iana-questions@icann.org> 2008-06-18
int
eu.int
@@ -1487,7 +1491,7 @@ gov.is
org.is
int.is
-// it : https://en.wikipedia.org/wiki/.it
+// it : https://www.iana.org/domains/root/db/it.html
it
gov.it
edu.it
@@ -1941,10 +1945,10 @@ gov.jo
mil.jo
name.jo
-// jobs : https://en.wikipedia.org/wiki/.jobs
+// jobs : https://www.iana.org/domains/root/db/jobs.html
jobs
-// jp : https://en.wikipedia.org/wiki/.jp
+// jp : https://www.iana.org/domains/root/db/jp.html
// http://jprs.co.jp/en/jpdomain.html
// Submitted by registry <info@jprs.jp>
jp
@@ -3825,7 +3829,7 @@ gov.ki
info.ki
com.ki
-// km : https://en.wikipedia.org/wiki/.km
+// km : https://www.iana.org/domains/root/db/km.html
// http://www.domaine.km/documents/charte.doc
km
org.km
@@ -3838,7 +3842,7 @@ mil.km
ass.km
com.km
// These are only mentioned as proposed suggestions at domaine.km, but
-// https://en.wikipedia.org/wiki/.km says they're available for registration:
+// https://www.iana.org/domains/root/db/km.html says they're available for registration:
coop.km
asso.km
presse.km
@@ -3848,7 +3852,7 @@ pharmaciens.km
veterinaire.km
gouv.km
-// kn : https://en.wikipedia.org/wiki/.kn
+// kn : https://www.iana.org/domains/root/db/kn.html
// http://www.dot.kn/domainRules.html
kn
net.kn
@@ -3865,7 +3869,7 @@ org.kp
rep.kp
tra.kp
-// kr : https://en.wikipedia.org/wiki/.kr
+// kr : https://www.iana.org/domains/root/db/kr.html
// see also: http://domain.nida.or.kr/eng/registration.jsp
kr
ac.kr
@@ -3918,7 +3922,7 @@ edu.ky
net.ky
org.ky
-// kz : https://en.wikipedia.org/wiki/.kz
+// kz : https://www.iana.org/domains/root/db/kz.html
// see also: http://www.nic.kz/rules/index.jsp
kz
org.kz
@@ -3928,7 +3932,7 @@ gov.kz
mil.kz
com.kz
-// la : https://en.wikipedia.org/wiki/.la
+// la : https://www.iana.org/domains/root/db/la.html
// Submitted by registry <gavin.brown@nic.la>
la
int.la
@@ -3940,7 +3944,7 @@ per.la
com.la
org.la
-// lb : https://en.wikipedia.org/wiki/.lb
+// lb : https://www.iana.org/domains/root/db/lb.html
// Submitted by registry <randy@psg.com>
lb
com.lb
@@ -3949,7 +3953,7 @@ gov.lb
net.lb
org.lb
-// lc : https://en.wikipedia.org/wiki/.lc
+// lc : https://www.iana.org/domains/root/db/lc.html
// see also: http://www.nic.lc/rules.htm
lc
com.lc
@@ -3959,7 +3963,7 @@ org.lc
edu.lc
gov.lc
-// li : https://en.wikipedia.org/wiki/.li
+// li : https://www.iana.org/domains/root/db/li.html
li
// lk : https://www.nic.lk/index.php/domain-registration/lk-domain-naming-structure
@@ -4002,7 +4006,7 @@ net.ls
org.ls
sc.ls
-// lt : https://en.wikipedia.org/wiki/.lt
+// lt : https://www.iana.org/domains/root/db/lt.html
lt
// gov.lt : http://www.gov.lt/index_en.php
gov.lt
@@ -4034,7 +4038,7 @@ med.ly
org.ly
id.ly
-// ma : https://en.wikipedia.org/wiki/.ma
+// ma : https://www.iana.org/domains/root/db/ma.html
// http://www.anrt.ma/fr/admin/download/upload/file_fr782.pdf
ma
co.ma
@@ -4049,10 +4053,10 @@ mc
tm.mc
asso.mc
-// md : https://en.wikipedia.org/wiki/.md
+// md : https://www.iana.org/domains/root/db/md.html
md
-// me : https://en.wikipedia.org/wiki/.me
+// me : https://www.iana.org/domains/root/db/me.html
me
co.me
net.me
@@ -4075,13 +4079,13 @@ mil.mg
com.mg
co.mg
-// mh : https://en.wikipedia.org/wiki/.mh
+// mh : https://www.iana.org/domains/root/db/mh.html
mh
-// mil : https://en.wikipedia.org/wiki/.mil
+// mil : https://www.iana.org/domains/root/db/mil.html
mil
-// mk : https://en.wikipedia.org/wiki/.mk
+// mk : https://www.iana.org/domains/root/db/mk.html
// see also: http://dns.marnet.net.mk/postapka.php
mk
com.mk
@@ -4093,7 +4097,7 @@ inf.mk
name.mk
// ml : http://www.gobin.info/domainname/ml-template.doc
-// see also: https://en.wikipedia.org/wiki/.ml
+// see also: https://www.iana.org/domains/root/db/ml.html
ml
com.ml
edu.ml
@@ -4103,10 +4107,10 @@ net.ml
org.ml
presse.ml
-// mm : https://en.wikipedia.org/wiki/.mm
+// mm : https://www.iana.org/domains/root/db/mm.html
*.mm
-// mn : https://en.wikipedia.org/wiki/.mn
+// mn : https://www.iana.org/domains/root/db/mn.html
mn
gov.mn
edu.mn
@@ -4120,17 +4124,17 @@ org.mo
edu.mo
gov.mo
-// mobi : https://en.wikipedia.org/wiki/.mobi
+// mobi : https://www.iana.org/domains/root/db/mobi.html
mobi
// mp : http://www.dot.mp/
// Confirmed by registry <dcamacho@saipan.com> 2008-06-17
mp
-// mq : https://en.wikipedia.org/wiki/.mq
+// mq : https://www.iana.org/domains/root/db/mq.html
mq
-// mr : https://en.wikipedia.org/wiki/.mr
+// mr : https://www.iana.org/domains/root/db/mr.html
mr
gov.mr
@@ -4150,7 +4154,7 @@ edu.mt
net.mt
org.mt
-// mu : https://en.wikipedia.org/wiki/.mu
+// mu : https://www.iana.org/domains/root/db/mu.html
mu
com.mu
net.mu
@@ -4163,7 +4167,7 @@ or.mu
// museum : https://welcome.museum/wp-content/uploads/2018/05/20180525-Registration-Policy-MUSEUM-EN_VF-2.pdf https://welcome.museum/buy-your-dot-museum-2/
museum
-// mv : https://en.wikipedia.org/wiki/.mv
+// mv : https://www.iana.org/domains/root/db/mv.html
// "mv" included because, contra Wikipedia, google.mv exists.
mv
aero.mv
@@ -4257,13 +4261,13 @@ nc
asso.nc
nom.nc
-// ne : https://en.wikipedia.org/wiki/.ne
+// ne : https://www.iana.org/domains/root/db/ne.html
ne
-// net : https://en.wikipedia.org/wiki/.net
+// net : https://www.iana.org/domains/root/db/net.html
net
-// nf : https://en.wikipedia.org/wiki/.nf
+// nf : https://www.iana.org/domains/root/db/nf.html
nf
com.nf
net.nf
@@ -4306,7 +4310,7 @@ nom.ni
org.ni
web.ni
-// nl : https://en.wikipedia.org/wiki/.nl
+// nl : https://www.iana.org/domains/root/db/nl.html
// https://www.sidn.nl/
// ccTLD for the Netherlands
nl
@@ -5245,10 +5249,10 @@ org.nr
net.nr
com.nr
-// nu : https://en.wikipedia.org/wiki/.nu
+// nu : https://www.iana.org/domains/root/db/nu.html
nu
-// nz : https://en.wikipedia.org/wiki/.nz
+// nz : https://www.iana.org/domains/root/db/nz.html
// Submitted by registry <jay@nzrs.net.nz>
nz
ac.nz
@@ -5269,7 +5273,7 @@ org.nz
parliament.nz
school.nz
-// om : https://en.wikipedia.org/wiki/.om
+// om : https://www.iana.org/domains/root/db/om.html
om
co.om
com.om
@@ -5284,7 +5288,7 @@ pro.om
// onion : https://tools.ietf.org/html/rfc7686
onion
-// org : https://en.wikipedia.org/wiki/.org
+// org : https://www.iana.org/domains/root/db/org.html
org
// pa : http://www.nic.pa/
@@ -5319,7 +5323,7 @@ com.pf
org.pf
edu.pf
-// pg : https://en.wikipedia.org/wiki/.pg
+// pg : https://www.iana.org/domains/root/db/pg.html
*.pg
// ph : http://www.domains.ph/FAQ2.asp
@@ -5582,7 +5586,7 @@ org.pn
edu.pn
net.pn
-// post : https://en.wikipedia.org/wiki/.post
+// post : https://www.iana.org/domains/root/db/post.html
post
// pr : http://www.nic.pr/index.asp?f=1
@@ -5597,7 +5601,7 @@ pro.pr
biz.pr
info.pr
name.pr
-// these aren't mentioned on nic.pr, but on https://en.wikipedia.org/wiki/.pr
+// these aren't mentioned on nic.pr, but on https://www.iana.org/domains/root/db/pr.html
est.pr
prof.pr
ac.pr
@@ -5616,7 +5620,7 @@ law.pro
med.pro
recht.pro
-// ps : https://en.wikipedia.org/wiki/.ps
+// ps : https://www.iana.org/domains/root/db/ps.html
// http://www.nic.ps/registration/policy.html#reg
ps
edu.ps
@@ -5638,7 +5642,7 @@ publ.pt
com.pt
nome.pt
-// pw : https://en.wikipedia.org/wiki/.pw
+// pw : https://www.iana.org/domains/root/db/pw.html
pw
co.pw
ne.pw
@@ -5752,7 +5756,7 @@ tv.sd
gov.sd
info.sd
-// se : https://en.wikipedia.org/wiki/.se
+// se : https://www.iana.org/domains/root/db/se.html
// Submitted by registry <patrik.wallstrom@iis.se>
se
a.se
@@ -5812,14 +5816,14 @@ gov.sh
org.sh
mil.sh
-// si : https://en.wikipedia.org/wiki/.si
+// si : https://www.iana.org/domains/root/db/si.html
si
// sj : No registrations at this time.
// Submitted by registry <jarle@uninett.no>
sj
-// sk : https://en.wikipedia.org/wiki/.sk
+// sk : https://www.iana.org/domains/root/db/sk.html
// list of 2nd level domains ?
sk
@@ -5832,10 +5836,10 @@ edu.sl
gov.sl
org.sl
-// sm : https://en.wikipedia.org/wiki/.sm
+// sm : https://www.iana.org/domains/root/db/sm.html
sm
-// sn : https://en.wikipedia.org/wiki/.sn
+// sn : https://www.iana.org/domains/root/db/sn.html
sn
art.sn
com.sn
@@ -5854,7 +5858,7 @@ me.so
net.so
org.so
-// sr : https://en.wikipedia.org/wiki/.sr
+// sr : https://www.iana.org/domains/root/db/sr.html
sr
// ss : https://registry.nic.ss/
@@ -5883,7 +5887,7 @@ principe.st
saotome.st
store.st
-// su : https://en.wikipedia.org/wiki/.su
+// su : https://www.iana.org/domains/root/db/su.html
su
// sv : http://www.svnet.org.sv/niveldos.pdf
@@ -5894,12 +5898,12 @@ gob.sv
org.sv
red.sv
-// sx : https://en.wikipedia.org/wiki/.sx
+// sx : https://www.iana.org/domains/root/db/sx.html
// Submitted by registry <jcvignes@openregistry.com>
sx
gov.sx
-// sy : https://en.wikipedia.org/wiki/.sy
+// sy : https://www.iana.org/domains/root/db/sy.html
// see also: http://www.gobin.info/domainname/sy.doc
sy
edu.sy
@@ -5909,31 +5913,31 @@ mil.sy
com.sy
org.sy
-// sz : https://en.wikipedia.org/wiki/.sz
+// sz : https://www.iana.org/domains/root/db/sz.html
// http://www.sispa.org.sz/
sz
co.sz
ac.sz
org.sz
-// tc : https://en.wikipedia.org/wiki/.tc
+// tc : https://www.iana.org/domains/root/db/tc.html
tc
-// td : https://en.wikipedia.org/wiki/.td
+// td : https://www.iana.org/domains/root/db/td.html
td
-// tel: https://en.wikipedia.org/wiki/.tel
+// tel: https://www.iana.org/domains/root/db/tel.html
// http://www.telnic.org/
tel
// tf : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf
tf
-// tg : https://en.wikipedia.org/wiki/.tg
+// tg : https://www.iana.org/domains/root/db/tg.html
// http://www.nic.tg/
tg
-// th : https://en.wikipedia.org/wiki/.th
+// th : https://www.iana.org/domains/root/db/th.html
// Submitted by registry <krit@thains.co.th>
th
ac.th
@@ -5962,10 +5966,10 @@ org.tj
test.tj
web.tj
-// tk : https://en.wikipedia.org/wiki/.tk
+// tk : https://www.iana.org/domains/root/db/tk.html
tk
-// tl : https://en.wikipedia.org/wiki/.tl
+// tl : https://www.iana.org/domains/root/db/tl.html
tl
gov.tl
@@ -5997,7 +6001,7 @@ org.tn
perso.tn
tourism.tn
-// to : https://en.wikipedia.org/wiki/.to
+// to : https://www.iana.org/domains/root/db/to.html
// Submitted by registry <egullich@colo.to>
to
com.to
@@ -6057,12 +6061,12 @@ name.tt
gov.tt
edu.tt
-// tv : https://en.wikipedia.org/wiki/.tv
+// tv : https://www.iana.org/domains/root/db/tv.html
// Not listing any 2LDs as reserved since none seem to exist in practice,
// Wikipedia notwithstanding.
tv
-// tw : https://en.wikipedia.org/wiki/.tw
+// tw : https://www.iana.org/domains/root/db/tw.html
tw
edu.tw
gov.tw
@@ -6194,7 +6198,7 @@ ne.ug
com.ug
org.ug
-// uk : https://en.wikipedia.org/wiki/.uk
+// uk : https://www.iana.org/domains/root/db/uk.html
// Submitted by registry <Michael.Daly@nominet.org.uk>
uk
ac.uk
@@ -6209,7 +6213,7 @@ plc.uk
police.uk
*.sch.uk
-// us : https://en.wikipedia.org/wiki/.us
+// us : https://www.iana.org/domains/root/db/us.html
us
dni.us
fed.us
@@ -6477,10 +6481,10 @@ com.uz
net.uz
org.uz
-// va : https://en.wikipedia.org/wiki/.va
+// va : https://www.iana.org/domains/root/db/va.html
va
-// vc : https://en.wikipedia.org/wiki/.vc
+// vc : https://www.iana.org/domains/root/db/vc.html
// Submitted by registry <kshah@ca.afilias.info>
vc
com.vc
@@ -6514,7 +6518,7 @@ store.ve
tec.ve
web.ve
-// vg : https://en.wikipedia.org/wiki/.vg
+// vg : https://www.iana.org/domains/root/db/vg.html
vg
// vi : http://www.nic.vi/newdomainform.htm
@@ -6612,7 +6616,7 @@ vinhlong.vn
vinhphuc.vn
yenbai.vn
-// vu : https://en.wikipedia.org/wiki/.vu
+// vu : https://www.iana.org/domains/root/db/vu.html
// http://www.vunic.vu/
vu
com.vu
@@ -6623,7 +6627,7 @@ org.vu
// wf : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf
wf
-// ws : https://en.wikipedia.org/wiki/.ws
+// ws : https://www.iana.org/domains/root/db/ws.html
// http://samoanic.ws/index.dhtml
ws
com.ws
@@ -7060,7 +7064,7 @@ org.zw
// newGTLDs
-// List of new gTLDs imported from https://www.icann.org/resources/registries/gtlds/v2/gtlds.json on 2024-08-10T15:15:39Z
+// List of new gTLDs imported from https://www.icann.org/resources/registries/gtlds/v2/gtlds.json on 2024-09-26T15:17:07Z
// This list is auto-generated, don't edit it manually.
// aaa : American Automobile Association, Inc.
// https://www.iana.org/domains/root/db/aaa.html
@@ -8006,10 +8010,6 @@ cymru
// https://www.iana.org/domains/root/db/cyou.html
cyou
-// dabur : Dabur India Limited
-// https://www.iana.org/domains/root/db/dabur.html
-dabur
-
// dad : Charleston Road Registry Inc.
// https://www.iana.org/domains/root/db/dad.html
dad
@@ -8462,7 +8462,7 @@ forex
// https://www.iana.org/domains/root/db/forsale.html
forsale
-// forum : Fegistry, LLC
+// forum : Waterford Limited
// https://www.iana.org/domains/root/db/forum.html
forum
@@ -9454,6 +9454,10 @@ men
// https://www.iana.org/domains/root/db/menu.html
menu
+// merck : Merck Registry Holdings, Inc.
+// https://www.iana.org/domains/root/db/merck.html
+merck
+
// merckmsd : MSD Registry Holdings, Inc.
// https://www.iana.org/domains/root/db/merckmsd.html
merckmsd
@@ -10034,7 +10038,7 @@ realestate
// https://www.iana.org/domains/root/db/realtor.html
realtor
-// realty : Internet Naming Company LLC
+// realty : Waterford Limited
// https://www.iana.org/domains/root/db/realty.html
realty
@@ -11612,7 +11616,6 @@ zone
// https://www.iana.org/domains/root/db/zuerich.html
zuerich
-
// ===END ICANN DOMAINS===
// ===BEGIN PRIVATE DOMAINS===
@@ -12528,7 +12531,7 @@ eero-stage.online
// Submitted by Apigee Security Team <security@apigee.com>
apigee.io
-// Apis Networks: https://apisnetworks.com
+// Apis Networks : https://apisnetworks.com
// Submitted by Matt Saladna <matt@apisnetworks.com>
panel.dev
@@ -12582,10 +12585,6 @@ cdn.prod.atlassian-dev.net
// Submitted by Lukas Reschke <lukas@authentick.net>
translated.page
-// Autocode : https://autocode.com
-// Submitted by Jacob Lee <jacob@autocode.com>
-autocode.dev
-
// AVM : https://avm.de
// Submitted by Andreas Weise <a.weise@avm.de>
myfritz.link
@@ -12600,7 +12599,7 @@ onavstack.net
*.awdev.ca
*.advisor.ws
-// AZ.pl sp. z.o.o: https://az.pl
+// AZ.pl sp. z.o.o : https://az.pl
// Submitted by Krzysztof Wolski <krzysztof.wolski@home.eu>
ecommerce-shop.pl
@@ -12651,10 +12650,6 @@ betainabox.com
// Submitted by Nathan O'Sullivan <nathan@mammoth.com.au>
bnr.la
-// Bip : https://bip.sh
-// Submitted by Joel Kennedy <joel@bip.sh>
-bip.sh
-
// Bitbucket : http://bitbucket.org
// Submitted by Andy Ortlieb <aortlieb@atlassian.com>
bitbucket.io
@@ -12720,10 +12715,6 @@ vm.bytemark.co.uk
// Submitted by Antonio Lain <antlai@cafjs.com>
cafjs.com
-// callidomus : https://www.callidomus.com/
-// Submitted by Marcus Popp <admin@callidomus.com>
-mycd.eu
-
// Canva Pty Ltd : https://canva.com/
// Submitted by Joel Aquilina <publicsuffixlist@canva.com>
canva-apps.cn
@@ -12776,10 +12767,6 @@ uk.net
ae.org
com.se
-// certmgr.org : https://certmgr.org
-// Submitted by B. Blechschmidt <hostmaster@certmgr.org>
-certmgr.org
-
// Cityhost LLC : https://cityhost.ua
// Submitted by Maksym Rivtin <support@cityhost.net.ua>
cx.ua
@@ -12907,6 +12894,10 @@ co.no
webhosting.be
hosting-cluster.nl
+// Contentful GmbH : https://www.contentful.com
+// Submitted by Contentful Developer Experience Team <prd-ecosystem-dx@contentful.com>
+ctfcloud.net
+
// Convex : https://convex.dev/
// Submitted by James Cowling <security@convex.dev>
convex.site
@@ -12952,14 +12943,6 @@ on.crisp.email
// Submitted by Marvin Wiesner <Marvin@curv-labs.de>
curv.dev
-// Customer OCI - Oracle Dyn https://cloud.oracle.com/home https://dyn.com/dns/
-// Submitted by Gregory Drake <support@dyn.com>
-// Note: This is intended to also include customer-oci.com due to wildcards implicitly including the current label
-*.customer-oci.com
-*.oci.customer-oci.com
-*.ocp.customer-oci.com
-*.ocs.customer-oci.com
-
// cyber_Folks S.A. : https://cyberfolks.pl
// Submitted by Bartlomiej Kida <security@cyberfolks.pl>
cfolks.pl
@@ -12983,20 +12966,10 @@ firm.dk
reg.dk
store.dk
-// Daplie, Inc : https://daplie.com
-// Submitted by AJ ONeal <aj@daplie.com>
-daplie.me
-localhost.daplie.me
-
// dappnode.io : https://dappnode.io/
// Submitted by Abel Boldu / DAppNode Team <community@dappnode.io>
dyndns.dappnode.io
-// dapps.earth : https://dapps.earth/
-// Submitted by Daniil Burdakov <icqkill@gmail.com>
-*.dapps.earth
-*.bzz.dapps.earth
-
// Dark, Inc. : https://darklang.com
// Submitted by Paul Biggar <ops@darklang.com>
builtwithdark.com
@@ -13079,8 +13052,8 @@ us.kg
// Diher Solutions : https://diher.solutions
// Submitted by Didi Hermawan <mail@diher.solutions>
-*.rss.my.id
-*.diher.solutions
+rss.my.id
+diher.solutions
// Discord Inc : https://discord.com
// Submitted by Sahn Lam <slam@discordapp.com>
@@ -13437,7 +13410,6 @@ freeddns.org
mywire.org
webredirect.org
myddns.rocks
-blogsite.xyz
// dynv6 : https://dynv6.com
// Submitted by Dominik Menke <dom@digineo.de>
@@ -13456,7 +13428,7 @@ easypanel.host
// Submitted by <infracloudteam@namecheap.com>
*.ewp.live
-// ECG Robotics, Inc: https://ecgrobotics.org
+// ECG Robotics, Inc : https://ecgrobotics.org
// Submitted by <frc1533@ecgrobotics.org>
onred.one
staging.onred.one
@@ -13536,7 +13508,6 @@ kr.eu.org
lt.eu.org
lu.eu.org
lv.eu.org
-mc.eu.org
me.eu.org
mk.eu.org
mt.eu.org
@@ -13726,14 +13697,6 @@ filegear.me
// Submitted by Chris Raynor <chris@firebase.com>
firebaseapp.com
-// Firewebkit : https://www.firewebkit.com
-// Submitted by Majid Qureshi <mqureshi@amrayn.com>
-fireweb.app
-
-// FLAP : https://www.flap.cloud
-// Submitted by Louis Chemineau <louis@chmn.me>
-flap.id
-
// FlashDrive : https://flashdrive.io
// Submitted by Eric Chan <support@flashdrive.io>
fldrv.com
@@ -13763,7 +13726,7 @@ framer.photos
framer.website
framer.wiki
-// Frederik Braun https://frederik-braun.com
+// Frederik Braun : https://frederik-braun.com
// Submitted by Frederik Braun <fb@frederik-braun.com>
0e.vc
@@ -14035,7 +13998,6 @@ codespot.com
googleapis.com
googlecode.com
pagespeedmobilizer.com
-publishproxy.com
withgoogle.com
withyoutube.com
blogspot.cv
@@ -14146,6 +14108,14 @@ häkkinen.fi
hs.run
hs.zone
+// Harrison Network : https://hrsn.net
+// Submitted by William Harrison <psl@hrsn.net>
+wdh.app
+preview.wdh.app
+hrsn.dev
+t.hrsn.dev
+t.hrsn.net
+
// Hashbang : https://hashbang.sh
hashbang.sh
@@ -14214,21 +14184,31 @@ hoplix.shop
// Submitted by Atanunu Igbunuroghene <publicsuffixlist@hostbip.com>
orx.biz
biz.gl
+biz.ng
+co.biz.ng
+dl.biz.ng
+go.biz.ng
+lg.biz.ng
+on.biz.ng
col.ng
firm.ng
gen.ng
ltd.ng
ngo.ng
-edu.scot
-sch.so
+plc.ng
// HostFly : https://www.ie.ua
// Submitted by Bohdan Dub <support@hostfly.com.ua>
ie.ua
-// HostyHosting (https://hostyhosting.com)
+// HostyHosting : https://hostyhosting.com
hostyhosting.io
+// Hugging Face: https://huggingface.co
+// Submitted by Eliott Coyac <website@huggingface.co>
+hf.space
+static.hf.space
+
// Hypernode B.V. : https://www.hypernode.com/
// Submitted by Cipriano Groenendal <security@nl.team.blue>
hypernode.io
@@ -14254,12 +14234,12 @@ gr.com
// Submitted by Hannu Aronsson <haa@iki.fi>
iki.fi
-// iliad italia: https://www.iliad.it
+// iliad italia : https://www.iliad.it
// Submitted by Marios Makassikis <mmakassikis@freebox.fr>
ibxos.it
iliadboxos.it
-// Incsub, LLC: https://incsub.com/
+// Incsub, LLC : https://incsub.com/
// Submitted by Aaron Edwards <sysadmins@incsub.com>
smushcdn.com
wphostedmail.com
@@ -14322,7 +14302,7 @@ to.leg.br
// Submitted by Wolfgang Schwarz <admin@intermetrics.de>
pixolino.com
-// Internet-Pro, LLP: https://netangels.ru/
+// Internet-Pro, LLP : https://netangels.ru/
// Submitted by Vasiliy Sheredeko <piphon@gmail.com>
na4u.ru
@@ -14339,6 +14319,10 @@ app-ionos.space
// Submitted by Roman Azarenko <roman.azarenko@iopsys.eu>
iopsys.se
+// IPFS Project : https://ipfs.tech/
+// Submitted by Interplanetary Shipyard <domains@ipshipyard.com>
+*.dweb.link
+
// IPiFony Systems, Inc. : https://www.ipifony.com/
// Submitted by Matthew Hardeman <mhardeman@ipifony.com>
ipifony.net
@@ -14347,6 +14331,10 @@ ipifony.net
// Submitted by Ali Soizi <info@nic.ir.md>
ir.md
+// is-a-good.dev : https://is-a-good.dev
+// Submitted by William Harrison <webmaster@is-a-good.dev>
+is-a-good.dev
+
// is-a.dev : https://www.is-a.dev
// Submitted by William Harrison <admin@m.is-a.dev>
is-a.dev
@@ -14364,7 +14352,6 @@ iserv.dev
// Submitted by Ihor Kolodyuk <ik@jelastic.com>
mel.cloudlets.com.au
cloud.interhostsolutions.be
-mycloud.by
alp1.ae.flow.ch
appengine.flow.ch
es-1.axarnet.cloud
@@ -14386,7 +14373,6 @@ us.reclaim.cloud
ch.trendhosting.cloud
de.trendhosting.cloud
jele.club
-amscompute.com
dopaas.com
paas.hosted-by-previder.com
rag-cloud.hosteur.com
@@ -14394,10 +14380,8 @@ rag-cloud-ch.hosteur.com
jcloud.ik-server.com
jcloud-ver-jpc.ik-server.com
demo.jelastic.com
-kilatiron.com
paas.massivegrid.com
jed.wafaicloud.com
-lon.wafaicloud.com
ryd.wafaicloud.com
j.scaleforce.com.cy
jelastic.dogado.eu
@@ -14409,18 +14393,14 @@ mircloud.host
paas.beebyte.io
sekd1.beebyteapp.io
jele.io
-cloud-fr1.unispace.io
jc.neen.it
-cloud.jelastic.open.tim.it
jcloud.kz
-upaas.kazteleport.kz
cloudjiffy.net
fra1-de.cloudjiffy.net
west1-us.cloudjiffy.net
jls-sto1.elastx.net
jls-sto2.elastx.net
jls-sto3.elastx.net
-faststacks.net
fr-1.paas.massivegrid.net
lon-1.paas.massivegrid.net
lon-2.paas.massivegrid.net
@@ -14430,11 +14410,9 @@ sg-1.paas.massivegrid.net
jelastic.saveincloud.net
nordeste-idc.saveincloud.net
j.scaleforce.net
-jelastic.tsukaeru.net
sdscloud.pl
unicloud.pl
mircloud.ru
-jelastic.regruhosting.ru
enscaled.sg
jele.site
jelastic.team
@@ -14474,10 +14452,6 @@ js.org
kaas.gg
khplay.nl
-// Kakao : https://www.kakaocorp.com/
-// Submitted by JaeYoong Lee <cec@kakaocorp.com>
-ktistory.com
-
// Kapsi : https://kapsi.fi
// Submitted by Tomi Juntunen <erani@kapsi.fi>
kapsi.fi
@@ -14530,7 +14504,7 @@ lpusercontent.com
lelux.site
// libp2p project : https://libp2p.io
-// Submitted by Interplanetary Shipyard <psl@ipshipyard.com>
+// Submitted by Interplanetary Shipyard <domains@ipshipyard.com>
libp2p.direct
// Libre IT Ltd : https://libre.nz
@@ -14572,10 +14546,6 @@ ggff.net
// Submitted by Lann Martin <security@localcert.dev>
*.user.localcert.dev
-// localzone.xyz
-// Submitted by Kenny Niehage <hello@yahe.sh>
-localzone.xyz
-
// Log'in Line : https://www.loginline.com/
// Submitted by Rémi Mach <remi.mach@loginline.com>
loginline.app
@@ -14648,6 +14618,13 @@ barsyonline.co.uk
// Submitted by Ilya Zaretskiy <zaretskiy@corp.mail.ru>
hb.cldmail.ru
+// MathWorks : https://www.mathworks.com/
+// Submitted by Emily Reed <psl-maintainers@groups.mathworks.com>
+matlab.cloud
+modelscape.com
+mwcloudnonprod.com
+polyspace.com
+
// May First - People Link : https://mayfirst.org/
// Submitted by Jamie McClelland <info@mayfirst.org>
mayfirst.info
@@ -14692,12 +14669,9 @@ atmeta.com
apps.fbsbx.com
// MetaCentrum, CESNET z.s.p.o. : https://www.metacentrum.cz/en/
-// Submitted by Zdeněk Šustr <zdenek.sustr@cesnet.cz>
+// Submitted by Zdeněk Šustr <zdenek.sustr@cesnet.cz> and Radim Janča <janca@cesnet.cz>
*.cloud.metacentrum.cz
custom.metacentrum.cz
-
-// MetaCentrum, CESNET z.s.p.o. : https://www.metacentrum.cz/en/
-// Submitted by Radim Janča <janca@cesnet.cz>
flt.cloud.muni.cz
usr.cloud.muni.cz
@@ -14739,15 +14713,23 @@ servicebus.windows.net
// MikroTik: https://mikrotik.com
// Submitted by MikroTik SysAdmin Team <support@mikrotik.com>
+routingthecloud.com
sn.mynetname.net
+routingthecloud.net
+routingthecloud.org
// minion.systems : http://minion.systems
// Submitted by Robert Böttinger <r@minion.systems>
csx.cc
-// MobileEducation, LLC : https://joinforte.com
-// Submitted by Grayson Martin <grayson.martin@mobileeducation.us>
-forte.id
+// Mittwald CM Service GmbH & Co. KG : https://mittwald.de
+// Submitted by Marco Rieger <security@mittwald.de>
+mydbserver.com
+webspaceconfig.de
+mittwald.info
+mittwaldserver.info
+typo3server.info
+project.space
// MODX Systems LLC : https://modx.com
// Submitted by Elizabeth Southwell <elizabeth@modx.com>
@@ -14836,15 +14818,6 @@ torun.pl
nh-serv.co.uk
nimsite.uk
-// No longer operated by CentralNic, these entries should be adopted and/or removed by current operators
-// Submitted by Gavin Brown <gavin.brown@centralnic.com>
-ar.com
-hu.com
-kr.com
-no.com
-qc.com
-uy.com
-
// No-IP.com : https://noip.com/
// Submitted by Deven Reza <publicsuffixlist@noip.com>
mmafan.biz
@@ -14964,12 +14937,8 @@ dnsking.ch
mypi.co
n4t.co
001www.com
-ddnslive.com
myiphost.com
forumz.info
-16-b.it
-32-b.it
-64-b.it
soundcast.me
tcp4.me
dnsup.net
@@ -14983,20 +14952,18 @@ x443.pw
now-dns.top
ntdll.top
freeddns.us
-crafting.xyz
-zapto.xyz
// nsupdate.info : https://www.nsupdate.info/
// Submitted by Thomas Waldmann <info@nsupdate.info>
nsupdate.info
nerdpol.ovh
-// NYC.mn : http://www.information.nyc.mn
-// Submitted by Matthew Brown <mattbrown@nyc.mn>
+// NYC.mn : https://dot.nyc.mn/
+// Submitted by NYC.mn Subdomain Service <nyc.mn@mailfence.com>
nyc.mn
// O3O.Foundation : https://o3o.foundation/
-// Submitted by the prvcy.page Registry Team <psl@registry.prvcy.page>
+// Submitted by the prvcy.page Registry Team <info@o3o.foundation>
prvcy.page
// Obl.ong : <https://obl.ong>
@@ -15008,7 +14975,7 @@ obl.ong
observablehq.cloud
static.observableusercontent.com
-// OMG.LOL : <https://omg.lol>
+// OMG.LOL : https://omg.lol
// Submitted by Adam Newbold <adam@omg.lol>
omg.lol
@@ -15058,6 +15025,12 @@ opensocial.site
// Submitted by Sven Marnach <sven@opencraft.com>
opencraft.hosting
+// OpenHost : https://registry.openhost.uk
+// Submitted by OpenHost Registry Team <support@openhost.uk>
+16-b.it
+32-b.it
+64-b.it
+
// OpenResearch GmbH: https://openresearch.com/
// Submitted by Philipp Schmid <ops@openresearch.com>
orsites.com
@@ -15066,6 +15039,17 @@ orsites.com
// Submitted by Yngve Pettersen <yngve@opera.com>
operaunite.com
+// Oracle Dyn : https://cloud.oracle.com/home https://dyn.com/dns/
+// Submitted by Gregory Drake <support@dyn.com>
+// Note: This is intended to also include customer-oci.com due to wildcards implicitly including the current label
+*.customer-oci.com
+*.oci.customer-oci.com
+*.ocp.customer-oci.com
+*.ocs.customer-oci.com
+*.oraclecloudapps.com
+*.oraclegovcloudapps.com
+*.oraclegovcloudapps.uk
+
// Orange : https://www.orange.com
// Submitted by Alexandre Linte <alexandre.linte@orange.com>
tech.orange
@@ -15169,10 +15153,6 @@ platterp.us
// Submitted by Henning Pohl <infra@pley.com>
pley.games
-// Port53 : https://port53.io/
-// Submitted by Maximilian Schieder <maxi@zeug.co>
-dyn53.io
-
// Porter : https://porter.run/
// Submitted by Rudraksh MK <rudi@porter.run>
onporter.run
@@ -15200,10 +15180,6 @@ xen.prgmr.com
// Submitted by registry <lendl@nic.at>
priv.at
-// Protocol Labs : https://protocol.ai/
-// Submitted by Michael Burns <noc@protocol.ai>
-*.dweb.link
-
// Protonet GmbH : http://protonet.io
// Submitted by Martin Meier <admin@protonet.io>
protonet.io
@@ -15248,7 +15224,7 @@ qoto.io
// Submitted by Xavier De Cock <xdecock@gmail.com>
qualifioapp.com
-// Quality Unit: https://qualityunit.com
+// Quality Unit : https://qualityunit.com
// Submitted by Vasyl Tsalko <vtsalko@qualityunit.com>
ladesk.com
@@ -15298,6 +15274,7 @@ ravpage.co.il
// Read The Docs, Inc : https://www.readthedocs.org
// Submitted by David Fischer <team@readthedocs.org>
+readthedocs-hosted.com
readthedocs.io
// Red Hat, Inc. OpenShift : https://openshift.redhat.com/
@@ -15359,11 +15336,6 @@ devices.resinstaging.io
// Submitted by Chris Kastorff <info@rethinkdb.com>
hzc.io
-// Revitalised Limited : http://www.revitalised.co.uk
-// Submitted by Jack Price <jack@revitalised.co.uk>
-wellbeingzone.eu
-wellbeingzone.co.uk
-
// Rico Developments Limited : https://adimo.co
// Submitted by Colin Brown <hello@adimo.co>
adimo.co.uk
@@ -15412,6 +15384,10 @@ xn--41a.xn--p1acf
// Submitted by Tech Support <support@rasnet.ru>
ras.ru
+// Sakura Frp : https://www.natfrp.com
+// Submitted by Bobo Liu <support@natfrp.cloud>
+nyat.app
+
// SAKURA Internet Inc. : https://www.sakura.ad.jp/
// Submitted by Internet Service Department <rs-vendor-ml@sakura.ad.jp>
180r.com
@@ -15567,10 +15543,6 @@ senseering.net
// Submitted by Daniel Kjeserud <cloudops@servebolt.com>
servebolt.cloud
-// Service Magnet : https://myservicemagnet.com
-// Submitted by Dave Sanders <dave@myservicemagnet.com>
-magnet.page
-
// Service Online LLC : http://drs.ua/
// Submitted by Serhii Bulakh <support@drs.ua>
biz.ua
@@ -15603,6 +15575,7 @@ shopitsite.com
// shopware AG : https://shopware.com
// Submitted by Jens Küper <cloud@shopware.com>
+shopware.shop
shopware.store
// Siemens Mobility GmbH
@@ -15621,12 +15594,6 @@ vipsinaapp.com
// Submitted by Skylar Challand <support@siteleaf.com>
siteleaf.net
-// Skyhat : http://www.skyhat.io
-// Submitted by Shante Adam <shante@skyhat.io>
-bounty-full.com
-alpha.bounty-full.com
-beta.bounty-full.com
-
// Small Technology Foundation : https://small-tech.org
// Submitted by Aral Balkan <aral@small-tech.org>
small-web.org
@@ -15801,11 +15768,6 @@ supabase.co
supabase.in
supabase.net
-// Symfony, SAS : https://symfony.com/
-// Submitted by Fabien Potencier <fabien@symfony.com>
-*.sensiosite.cloud
-*.s5y.io
-
// Syncloud : https://syncloud.org
// Submitted by Boris Rybalkin <syncloud@syncloud.it>
syncloud.it
@@ -15929,14 +15891,11 @@ webspace.rocks
lima.zone
// TransIP : https://www.transip.nl
-// Submitted by Rory Breuk <rbreuk@transip.nl>
+// Submitted by Rory Breuk <rbreuk@transip.nl> and Cedric Dubois <cedric.dubois@team.blue>
*.transurl.be
*.transurl.eu
-*.transurl.nl
-
-// TransIP: https://www.transip.nl
-// Submitted by Cedric Dubois <cedric.dubois@team.blue>
site.transip.me
+*.transurl.nl
// TuxFamily : http://tuxfamily.org
// Submitted by TuxFamily administrators <adm@staff.tuxfamily.org>
@@ -16032,9 +15991,11 @@ express.val.run
web.val.run
// Vercel, Inc : https://vercel.com/
-// Submitted by Connor Davis <security@vercel.com>
+// Submitted by Max Leiter <security@vercel.com>
vercel.app
+v0.build
vercel.dev
+vusercontent.net
now.sh
// VeryPositive SIA : http://very.lv
@@ -16066,11 +16027,11 @@ wafflecell.com
webflow.io
webflowtest.io
-// WebHare bv: https://www.webhare.com/
+// WebHare bv : https://www.webhare.com/
// Submitted by Arnold Hendriks <info@webhare.com>
*.webhare.dev
-// WebHotelier Technologies Ltd: https://www.webhotelier.net/
+// WebHotelier Technologies Ltd : https://www.webhotelier.net/
// Submitted by Apostolos Tsakpinis <apostolos.tsakpinis@gmail.com>
bookonline.app
hotelwithflight.com
@@ -16086,7 +16047,7 @@ pdns.page
plesk.page
wpsquared.site
-// WebWaddle Ltd: https://webwaddle.com/
+// WebWaddle Ltd : https://webwaddle.com/
// Submitted by Merlin Glander <hostmaster@webwaddle.com>
*.wadl.top
@@ -16108,10 +16069,6 @@ toolforge.org
wmcloud.org
wmflabs.org
-// William Harrison : https://wdh.gg
-// Submitted by William Harrison <domains@wdh.gg>
-*.wdh.app
-
// WISP : https://wisp.gg
// Submitted by Stepan Fedotov <stepan@wisp.gg>
panel.gg
@@ -16166,7 +16123,7 @@ cistron.nl
demon.nl
xs4all.space
-// Yandex.Cloud LLC: https://cloud.yandex.com
+// Yandex.Cloud LLC : https://cloud.yandex.com
// Submitted by Alexander Lodin <security+psl@yandex-team.ru>
yandexcloud.net
storage.yandexcloud.net
diff --git a/debian/rules b/debian/rules
index 74771ede3..be06aa6d6 100755
--- a/debian/rules
+++ b/debian/rules
@@ -39,7 +39,7 @@ configure_%:
-DSYSTEMDDIR=/lib/systemd/system \
-DCMAKE_INSTALL_PREFIX:PATH=/usr \
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
- -DDEBIAN_BUILD=1 \
+ -DNO_TARGET_VERSIONS=1 \
-DENABLE_PCRE2=ON \
-DENABLE_LIBUNWIND=ON \
-DWANT_SYSTEMD_UNITS=ON \
diff --git a/rpm/rspamd.spec b/rpm/rspamd.spec
index 7a1a15c92..663fa0929 100644
--- a/rpm/rspamd.spec
+++ b/rpm/rspamd.spec
@@ -136,7 +136,8 @@ rm -f %{_builddir}/luajit-build/lib/*.so || true
-DSYSTEMDDIR=%{_unitdir} \
-DWANT_SYSTEMD_UNITS=ON \
-DNO_SHARED=ON \
- -DDEBIAN_BUILD=1 \
+ -DNO_TARGET_VERSIONS=1 \
+ -DRSPAMD_LEGACY_SSL_PROVIDER=1 \
%ifarch x86_64 amd64 arm64 aarch64
-DENABLE_HYPERSCAN=ON \
%endif
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 173917703..f7fdcef7b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -235,9 +235,9 @@ ADD_EXECUTABLE(rspamd ${RSPAMDSRC} ${CMAKE_CURRENT_BINARY_DIR}/workers.c ${CMAKE
ADD_BACKWARD(rspamd)
SET_TARGET_PROPERTIES(rspamd PROPERTIES LINKER_LANGUAGE CXX)
SET_TARGET_PROPERTIES(rspamd-server PROPERTIES LINKER_LANGUAGE CXX)
-IF(NOT DEBIAN_BUILD)
+IF(NOT NO_TARGET_VERSIONS)
SET_TARGET_PROPERTIES(rspamd PROPERTIES VERSION ${RSPAMD_VERSION})
-ENDIF(NOT DEBIAN_BUILD)
+ENDIF()
#TARGET_LINK_LIBRARIES(rspamd ${RSPAMD_REQUIRED_LIBRARIES})
TARGET_LINK_LIBRARIES(rspamd rspamd-server)
diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt
index edf3cc1c4..543fc629c 100644
--- a/src/client/CMakeLists.txt
+++ b/src/client/CMakeLists.txt
@@ -9,8 +9,8 @@ SET_TARGET_PROPERTIES(rspamc PROPERTIES COMPILE_FLAGS "-I${CMAKE_SOURCE_DIR}/lib
TARGET_LINK_LIBRARIES(rspamc rspamd-server)
SET_TARGET_PROPERTIES(rspamc PROPERTIES LINKER_LANGUAGE CXX)
-IF(NOT DEBIAN_BUILD)
+IF(NOT NO_TARGET_VERSIONS)
SET_TARGET_PROPERTIES(rspamc PROPERTIES VERSION ${RSPAMD_VERSION})
-ENDIF(NOT DEBIAN_BUILD)
+ENDIF()
INSTALL(TARGETS rspamc RUNTIME DESTINATION bin)
diff --git a/src/client/rspamdclient.c b/src/client/rspamdclient.c
index bcb3cf67c..d07b24332 100644
--- a/src/client/rspamdclient.c
+++ b/src/client/rspamdclient.c
@@ -441,6 +441,7 @@ rspamd_client_command(struct rspamd_client_connection *conn,
if (compressed) {
rspamd_http_message_add_header(req->msg, COMPRESSION_HEADER, "zstd");
+ rspamd_http_message_add_header(req->msg, CONTENT_ENCODING_HEADER, "zstd");
if (dict_id != 0) {
char dict_str[32];
diff --git a/src/libcryptobox/cryptobox.c b/src/libcryptobox/cryptobox.c
index a976653df..190d0e4a3 100644
--- a/src/libcryptobox/cryptobox.c
+++ b/src/libcryptobox/cryptobox.c
@@ -40,6 +40,7 @@
#include <openssl/opensslv.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
+#include <openssl/err.h>
#endif
#include <signal.h>
@@ -456,9 +457,10 @@ bool rspamd_cryptobox_verify_evp_rsa(int nid,
gsize siglen,
const unsigned char *digest,
gsize dlen,
- EVP_PKEY *pub_key)
+ EVP_PKEY *pub_key,
+ GError **err)
{
- bool ret = false;
+ bool ret = false, r;
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(pub_key, NULL);
g_assert(pctx != NULL);
@@ -467,7 +469,18 @@ bool rspamd_cryptobox_verify_evp_rsa(int nid,
g_assert(EVP_PKEY_verify_init(pctx) == 1);
g_assert(EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) == 1);
- g_assert(EVP_PKEY_CTX_set_signature_md(pctx, md) == 1);
+
+ if ((r = EVP_PKEY_CTX_set_signature_md(pctx, md)) <= 0) {
+ g_set_error(err, g_quark_from_static_string("OpenSSL"),
+ r,
+ "cannot set digest %s for RSA verification (%s returned from OpenSSL), try use `update-crypto-policies --set LEGACY` on RH",
+ EVP_MD_name(md),
+ ERR_lib_error_string(ERR_get_error()));
+ EVP_PKEY_CTX_free(pctx);
+ EVP_MD_CTX_free(mdctx);
+
+ return false;
+ }
ret = (EVP_PKEY_verify(pctx, sig, siglen, digest, dlen) == 1);
diff --git a/src/libcryptobox/cryptobox.h b/src/libcryptobox/cryptobox.h
index afe9c4f9a..8d1f5669e 100644
--- a/src/libcryptobox/cryptobox.h
+++ b/src/libcryptobox/cryptobox.h
@@ -238,7 +238,8 @@ bool rspamd_cryptobox_verify_evp_rsa(int nid,
gsize siglen,
const unsigned char *digest,
gsize dlen,
- EVP_PKEY *pub_key);
+ EVP_PKEY *pub_key,
+ GError **err);
#endif
/**
diff --git a/src/libserver/dkim.c b/src/libserver/dkim.c
index a76ed31ab..0f51c66c0 100644
--- a/src/libserver/dkim.c
+++ b/src/libserver/dkim.c
@@ -2871,25 +2871,48 @@ rspamd_dkim_check(rspamd_dkim_context_t *ctx,
nid = NID_sha1;
}
switch (key->type) {
- case RSPAMD_DKIM_KEY_RSA:
+ case RSPAMD_DKIM_KEY_RSA: {
+ GError *err = NULL;
+
if (!rspamd_cryptobox_verify_evp_rsa(nid, ctx->b, ctx->blen, raw_digest, dlen,
- key->specific.key_ssl.key_evp)) {
- msg_debug_dkim("headers rsa verify failed");
- ERR_clear_error();
- res->rcode = DKIM_REJECT;
- res->fail_reason = "headers rsa verify failed";
+ key->specific.key_ssl.key_evp, &err)) {
- msg_info_dkim(
- "%s: headers RSA verification failure; "
- "body length %d->%d; headers length %d; d=%s; s=%s; key_md5=%*xs; orig header: %s",
- rspamd_dkim_type_to_string(ctx->common.type),
- (int) (body_end - body_start), ctx->common.body_canonicalised,
- ctx->common.headers_canonicalised,
- ctx->domain, ctx->selector,
- RSPAMD_DKIM_KEY_ID_LEN, rspamd_dkim_key_id(key),
- ctx->dkim_header);
+ if (err == NULL) {
+ msg_debug_dkim("headers rsa verify failed");
+ ERR_clear_error();
+ res->rcode = DKIM_REJECT;
+ res->fail_reason = "headers rsa verify failed";
+
+ msg_info_dkim(
+ "%s: headers RSA verification failure; "
+ "body length %d->%d; headers length %d; d=%s; s=%s; key_md5=%*xs; orig header: %s",
+ rspamd_dkim_type_to_string(ctx->common.type),
+ (int) (body_end - body_start), ctx->common.body_canonicalised,
+ ctx->common.headers_canonicalised,
+ ctx->domain, ctx->selector,
+ RSPAMD_DKIM_KEY_ID_LEN, rspamd_dkim_key_id(key),
+ ctx->dkim_header);
+ }
+ else {
+ res->rcode = DKIM_PERM_ERROR;
+ res->fail_reason = "openssl internal error";
+ msg_err_dkim("internal OpenSSL error: %s", err->message);
+ msg_info_dkim(
+ "%s: headers RSA verification failure due to OpenSSL internal error; "
+ "body length %d->%d; headers length %d; d=%s; s=%s; key_md5=%*xs; orig header: %s",
+ rspamd_dkim_type_to_string(ctx->common.type),
+ (int) (body_end - body_start), ctx->common.body_canonicalised,
+ ctx->common.headers_canonicalised,
+ ctx->domain, ctx->selector,
+ RSPAMD_DKIM_KEY_ID_LEN, rspamd_dkim_key_id(key),
+ ctx->dkim_header);
+
+ ERR_clear_error();
+ g_error_free(err);
+ }
}
break;
+ }
case RSPAMD_DKIM_KEY_ECDSA:
if (rspamd_cryptobox_verify_evp_ecdsa(nid, ctx->b, ctx->blen, raw_digest, dlen,
key->specific.key_ssl.key_evp) != 1) {
diff --git a/src/libserver/protocol.c b/src/libserver/protocol.c
index a86111ff2..7d007370b 100644
--- a/src/libserver/protocol.c
+++ b/src/libserver/protocol.c
@@ -490,271 +490,271 @@ rspamd_protocol_handle_headers(struct rspamd_task *task,
hv_tok->len = h->value.len;
switch (*hn_tok->begin) {
- case 'd':
- case 'D':
- IF_HEADER(DELIVER_TO_HEADER)
- {
- task->deliver_to = rspamd_protocol_escape_braces(task, hv_tok);
- msg_debug_protocol("read deliver-to header, value: %s",
- task->deliver_to);
- }
- else
- {
- msg_debug_protocol("wrong header: %T", hn_tok);
- }
- break;
- case 'h':
- case 'H':
- IF_HEADER(HELO_HEADER)
- {
- task->helo = rspamd_mempool_ftokdup(task->task_pool, hv_tok);
- msg_debug_protocol("read helo header, value: %s", task->helo);
- }
- IF_HEADER(HOSTNAME_HEADER)
- {
- task->hostname = rspamd_mempool_ftokdup(task->task_pool,
- hv_tok);
- msg_debug_protocol("read hostname header, value: %s", task->hostname);
- }
- break;
- case 'f':
- case 'F':
- IF_HEADER(FROM_HEADER)
- {
- if (hv_tok->len == 0) {
- /* Replace '' with '<>' to fix parsing issue */
- RSPAMD_FTOK_ASSIGN(hv_tok, "<>");
+ case 'd':
+ case 'D':
+ IF_HEADER(DELIVER_TO_HEADER)
+ {
+ task->deliver_to = rspamd_protocol_escape_braces(task, hv_tok);
+ msg_debug_protocol("read deliver-to header, value: %s",
+ task->deliver_to);
}
- task->from_envelope = rspamd_email_address_from_smtp(
- hv_tok->begin,
- hv_tok->len);
- msg_debug_protocol("read from header, value: %T", hv_tok);
-
- if (!task->from_envelope) {
- msg_err_protocol("bad from header: '%T'", hv_tok);
- task->flags |= RSPAMD_TASK_FLAG_BROKEN_HEADERS;
+ else
+ {
+ msg_debug_protocol("wrong header: %T", hn_tok);
}
- }
- IF_HEADER(FILENAME_HEADER)
- {
- task->msg.fpath = rspamd_mempool_ftokdup(task->task_pool,
- hv_tok);
- msg_debug_protocol("read filename header, value: %s", task->msg.fpath);
- }
- IF_HEADER(FLAGS_HEADER)
- {
- msg_debug_protocol("read flags header, value: %T", hv_tok);
- rspamd_protocol_process_flags(task, hv_tok);
- }
- break;
- case 'q':
- case 'Q':
- IF_HEADER(QUEUE_ID_HEADER)
- {
- task->queue_id = rspamd_mempool_ftokdup(task->task_pool,
- hv_tok);
- msg_debug_protocol("read queue_id header, value: %s", task->queue_id);
- }
- else
- {
- msg_debug_protocol("wrong header: %T", hn_tok);
- }
- break;
- case 'r':
- case 'R':
- IF_HEADER(RCPT_HEADER)
- {
- rspamd_protocol_process_recipients(task, hv_tok);
- msg_debug_protocol("read rcpt header, value: %T", hv_tok);
- }
- IF_HEADER(RAW_DATA_HEADER)
- {
- srch.begin = "yes";
- srch.len = 3;
-
- msg_debug_protocol("read raw data header, value: %T", hv_tok);
+ break;
+ case 'h':
+ case 'H':
+ IF_HEADER(HELO_HEADER)
+ {
+ task->helo = rspamd_mempool_ftokdup(task->task_pool, hv_tok);
+ msg_debug_protocol("read helo header, value: %s", task->helo);
+ }
+ IF_HEADER(HOSTNAME_HEADER)
+ {
+ task->hostname = rspamd_mempool_ftokdup(task->task_pool,
+ hv_tok);
+ msg_debug_protocol("read hostname header, value: %s", task->hostname);
+ }
+ break;
+ case 'f':
+ case 'F':
+ IF_HEADER(FROM_HEADER)
+ {
+ if (hv_tok->len == 0) {
+ /* Replace '' with '<>' to fix parsing issue */
+ RSPAMD_FTOK_ASSIGN(hv_tok, "<>");
+ }
+ task->from_envelope = rspamd_email_address_from_smtp(
+ hv_tok->begin,
+ hv_tok->len);
+ msg_debug_protocol("read from header, value: %T", hv_tok);
- if (rspamd_ftok_casecmp(hv_tok, &srch) == 0) {
- task->flags &= ~RSPAMD_TASK_FLAG_MIME;
- msg_debug_protocol("disable mime parsing");
+ if (!task->from_envelope) {
+ msg_err_protocol("bad from header: '%T'", hv_tok);
+ task->flags |= RSPAMD_TASK_FLAG_BROKEN_HEADERS;
+ }
}
- }
- break;
- case 'i':
- case 'I':
- IF_HEADER(IP_ADDR_HEADER)
- {
- if (!rspamd_parse_inet_address(&task->from_addr,
- hv_tok->begin, hv_tok->len,
- RSPAMD_INET_ADDRESS_PARSE_DEFAULT)) {
- msg_err_protocol("bad ip header: '%T'", hv_tok);
+ IF_HEADER(FILENAME_HEADER)
+ {
+ task->msg.fpath = rspamd_mempool_ftokdup(task->task_pool,
+ hv_tok);
+ msg_debug_protocol("read filename header, value: %s", task->msg.fpath);
}
- else {
- msg_debug_protocol("read IP header, value: %T", hv_tok);
- has_ip = TRUE;
+ IF_HEADER(FLAGS_HEADER)
+ {
+ msg_debug_protocol("read flags header, value: %T", hv_tok);
+ rspamd_protocol_process_flags(task, hv_tok);
}
- }
- else
- {
- msg_debug_protocol("wrong header: %T", hn_tok);
- }
- break;
- case 'p':
- case 'P':
- IF_HEADER(PASS_HEADER)
- {
- srch.begin = "all";
- srch.len = 3;
+ break;
+ case 'q':
+ case 'Q':
+ IF_HEADER(QUEUE_ID_HEADER)
+ {
+ task->queue_id = rspamd_mempool_ftokdup(task->task_pool,
+ hv_tok);
+ msg_debug_protocol("read queue_id header, value: %s", task->queue_id);
+ }
+ else
+ {
+ msg_debug_protocol("wrong header: %T", hn_tok);
+ }
+ break;
+ case 'r':
+ case 'R':
+ IF_HEADER(RCPT_HEADER)
+ {
+ rspamd_protocol_process_recipients(task, hv_tok);
+ msg_debug_protocol("read rcpt header, value: %T", hv_tok);
+ }
+ IF_HEADER(RAW_DATA_HEADER)
+ {
+ srch.begin = "yes";
+ srch.len = 3;
- msg_debug_protocol("read pass header, value: %T", hv_tok);
+ msg_debug_protocol("read raw data header, value: %T", hv_tok);
- if (rspamd_ftok_casecmp(hv_tok, &srch) == 0) {
- task->flags |= RSPAMD_TASK_FLAG_PASS_ALL;
- msg_debug_protocol("pass all filters");
+ if (rspamd_ftok_casecmp(hv_tok, &srch) == 0) {
+ task->flags &= ~RSPAMD_TASK_FLAG_MIME;
+ msg_debug_protocol("disable mime parsing");
+ }
}
- }
- IF_HEADER(PROFILE_HEADER)
- {
- msg_debug_protocol("read profile header, value: %T", hv_tok);
- task->flags |= RSPAMD_TASK_FLAG_PROFILE;
- }
- break;
- case 's':
- case 'S':
- IF_HEADER(SETTINGS_ID_HEADER)
- {
- msg_debug_protocol("read settings-id header, value: %T", hv_tok);
- task->settings_elt = rspamd_config_find_settings_name_ref(
- task->cfg, hv_tok->begin, hv_tok->len);
-
- if (task->settings_elt == NULL) {
- GString *known_ids = g_string_new(NULL);
- struct rspamd_config_settings_elt *cur;
-
- DL_FOREACH(task->cfg->setting_ids, cur)
- {
- rspamd_printf_gstring(known_ids, "%s(%ud);",
- cur->name, cur->id);
+ break;
+ case 'i':
+ case 'I':
+ IF_HEADER(IP_ADDR_HEADER)
+ {
+ if (!rspamd_parse_inet_address(&task->from_addr,
+ hv_tok->begin, hv_tok->len,
+ RSPAMD_INET_ADDRESS_PARSE_DEFAULT)) {
+ msg_err_protocol("bad ip header: '%T'", hv_tok);
+ }
+ else {
+ msg_debug_protocol("read IP header, value: %T", hv_tok);
+ has_ip = TRUE;
}
+ }
+ else
+ {
+ msg_debug_protocol("wrong header: %T", hn_tok);
+ }
+ break;
+ case 'p':
+ case 'P':
+ IF_HEADER(PASS_HEADER)
+ {
+ srch.begin = "all";
+ srch.len = 3;
+
+ msg_debug_protocol("read pass header, value: %T", hv_tok);
+
+ if (rspamd_ftok_casecmp(hv_tok, &srch) == 0) {
+ task->flags |= RSPAMD_TASK_FLAG_PASS_ALL;
+ msg_debug_protocol("pass all filters");
+ }
+ }
+ IF_HEADER(PROFILE_HEADER)
+ {
+ msg_debug_protocol("read profile header, value: %T", hv_tok);
+ task->flags |= RSPAMD_TASK_FLAG_PROFILE;
+ }
+ break;
+ case 's':
+ case 'S':
+ IF_HEADER(SETTINGS_ID_HEADER)
+ {
+ msg_debug_protocol("read settings-id header, value: %T", hv_tok);
+ task->settings_elt = rspamd_config_find_settings_name_ref(
+ task->cfg, hv_tok->begin, hv_tok->len);
+
+ if (task->settings_elt == NULL) {
+ GString *known_ids = g_string_new(NULL);
+ struct rspamd_config_settings_elt *cur;
+
+ DL_FOREACH(task->cfg->setting_ids, cur)
+ {
+ rspamd_printf_gstring(known_ids, "%s(%ud);",
+ cur->name, cur->id);
+ }
- msg_warn_protocol("unknown settings id: %T(%d); known_ids: %v",
- hv_tok,
- rspamd_config_name_to_id(hv_tok->begin, hv_tok->len),
- known_ids);
+ msg_warn_protocol("unknown settings id: %T(%d); known_ids: %v",
+ hv_tok,
+ rspamd_config_name_to_id(hv_tok->begin, hv_tok->len),
+ known_ids);
- g_string_free(known_ids, TRUE);
+ g_string_free(known_ids, TRUE);
+ }
+ else {
+ msg_debug_protocol("applied settings id %T -> %ud", hv_tok,
+ task->settings_elt->id);
+ }
}
- else {
- msg_debug_protocol("applied settings id %T -> %ud", hv_tok,
- task->settings_elt->id);
+ IF_HEADER(SETTINGS_HEADER)
+ {
+ msg_debug_protocol("read settings header, value: %T", hv_tok);
+ seen_settings_header = TRUE;
}
- }
- IF_HEADER(SETTINGS_HEADER)
- {
- msg_debug_protocol("read settings header, value: %T", hv_tok);
- seen_settings_header = TRUE;
- }
- break;
- case 'u':
- case 'U':
- IF_HEADER(USER_HEADER)
- {
- /*
+ break;
+ case 'u':
+ case 'U':
+ IF_HEADER(USER_HEADER)
+ {
+ /*
* We must ignore User header in case of spamc, as SA has
* different meaning of this header
*/
- msg_debug_protocol("read user header, value: %T", hv_tok);
- if (!RSPAMD_TASK_IS_SPAMC(task)) {
- task->auth_user = rspamd_mempool_ftokdup(task->task_pool,
- hv_tok);
- }
- else {
- msg_info_protocol("ignore user header: legacy SA protocol");
+ msg_debug_protocol("read user header, value: %T", hv_tok);
+ if (!RSPAMD_TASK_IS_SPAMC(task)) {
+ task->auth_user = rspamd_mempool_ftokdup(task->task_pool,
+ hv_tok);
+ }
+ else {
+ msg_info_protocol("ignore user header: legacy SA protocol");
+ }
}
- }
- IF_HEADER(URLS_HEADER)
- {
- msg_debug_protocol("read urls header, value: %T", hv_tok);
+ IF_HEADER(URLS_HEADER)
+ {
+ msg_debug_protocol("read urls header, value: %T", hv_tok);
- srch.begin = "extended";
- srch.len = 8;
+ srch.begin = "extended";
+ srch.len = 8;
- if (rspamd_ftok_casecmp(hv_tok, &srch) == 0) {
- task->protocol_flags |= RSPAMD_TASK_PROTOCOL_FLAG_EXT_URLS;
- msg_debug_protocol("extended urls information");
- }
-
- /* TODO: add more formats there */
- }
- IF_HEADER(USER_AGENT_HEADER)
- {
- msg_debug_protocol("read user-agent header, value: %T", hv_tok);
+ if (rspamd_ftok_casecmp(hv_tok, &srch) == 0) {
+ task->protocol_flags |= RSPAMD_TASK_PROTOCOL_FLAG_EXT_URLS;
+ msg_debug_protocol("extended urls information");
+ }
- if (hv_tok->len == 6 &&
- rspamd_lc_cmp(hv_tok->begin, "rspamc", 6) == 0) {
- task->protocol_flags |= RSPAMD_TASK_PROTOCOL_FLAG_LOCAL_CLIENT;
+ /* TODO: add more formats there */
}
- }
- break;
- case 'l':
- case 'L':
- IF_HEADER(NO_LOG_HEADER)
- {
- msg_debug_protocol("read log header, value: %T", hv_tok);
- srch.begin = "no";
- srch.len = 2;
+ IF_HEADER(USER_AGENT_HEADER)
+ {
+ msg_debug_protocol("read user-agent header, value: %T", hv_tok);
- if (rspamd_ftok_casecmp(hv_tok, &srch) == 0) {
- task->flags |= RSPAMD_TASK_FLAG_NO_LOG;
+ if (hv_tok->len == 6 &&
+ rspamd_lc_cmp(hv_tok->begin, "rspamc", 6) == 0) {
+ task->protocol_flags |= RSPAMD_TASK_PROTOCOL_FLAG_LOCAL_CLIENT;
+ }
}
- }
- IF_HEADER(LOG_TAG_HEADER)
- {
- msg_debug_protocol("read log-tag header, value: %T", hv_tok);
- /* Ensure that a tag is valid */
- if (rspamd_fast_utf8_validate(hv_tok->begin, hv_tok->len) == 0) {
- memcpy(task->task_pool->tag.uid, hv_tok->begin,
- MIN(hv_tok->len, sizeof(task->task_pool->tag.uid)));
+ break;
+ case 'l':
+ case 'L':
+ IF_HEADER(NO_LOG_HEADER)
+ {
+ msg_debug_protocol("read log header, value: %T", hv_tok);
+ srch.begin = "no";
+ srch.len = 2;
+
+ if (rspamd_ftok_casecmp(hv_tok, &srch) == 0) {
+ task->flags |= RSPAMD_TASK_FLAG_NO_LOG;
+ }
}
- }
- break;
- case 'm':
- case 'M':
- IF_HEADER(MTA_TAG_HEADER)
- {
- char *mta_tag;
- mta_tag = rspamd_mempool_ftokdup(task->task_pool, hv_tok);
- rspamd_mempool_set_variable(task->task_pool,
- RSPAMD_MEMPOOL_MTA_TAG,
- mta_tag, NULL);
- msg_debug_protocol("read MTA-Tag header, value: %s", mta_tag);
- }
- IF_HEADER(MTA_NAME_HEADER)
- {
- char *mta_name;
- mta_name = rspamd_mempool_ftokdup(task->task_pool, hv_tok);
- rspamd_mempool_set_variable(task->task_pool,
- RSPAMD_MEMPOOL_MTA_NAME,
- mta_name, NULL);
- msg_debug_protocol("read MTA-Name header, value: %s", mta_name);
- }
- IF_HEADER(MILTER_HEADER)
- {
- task->protocol_flags |= RSPAMD_TASK_PROTOCOL_FLAG_MILTER;
- msg_debug_protocol("read Milter header, value: %T", hv_tok);
- }
- break;
- case 't':
- case 'T':
- IF_HEADER(TLS_CIPHER_HEADER)
- {
- task->flags |= RSPAMD_TASK_FLAG_SSL;
- msg_debug_protocol("read TLS cipher header, value: %T", hv_tok);
- }
- break;
- default:
- msg_debug_protocol("generic header: %T", hn_tok);
- break;
+ IF_HEADER(LOG_TAG_HEADER)
+ {
+ msg_debug_protocol("read log-tag header, value: %T", hv_tok);
+ /* Ensure that a tag is valid */
+ if (rspamd_fast_utf8_validate(hv_tok->begin, hv_tok->len) == 0) {
+ memcpy(task->task_pool->tag.uid, hv_tok->begin,
+ MIN(hv_tok->len, sizeof(task->task_pool->tag.uid)));
+ }
+ }
+ break;
+ case 'm':
+ case 'M':
+ IF_HEADER(MTA_TAG_HEADER)
+ {
+ char *mta_tag;
+ mta_tag = rspamd_mempool_ftokdup(task->task_pool, hv_tok);
+ rspamd_mempool_set_variable(task->task_pool,
+ RSPAMD_MEMPOOL_MTA_TAG,
+ mta_tag, NULL);
+ msg_debug_protocol("read MTA-Tag header, value: %s", mta_tag);
+ }
+ IF_HEADER(MTA_NAME_HEADER)
+ {
+ char *mta_name;
+ mta_name = rspamd_mempool_ftokdup(task->task_pool, hv_tok);
+ rspamd_mempool_set_variable(task->task_pool,
+ RSPAMD_MEMPOOL_MTA_NAME,
+ mta_name, NULL);
+ msg_debug_protocol("read MTA-Name header, value: %s", mta_name);
+ }
+ IF_HEADER(MILTER_HEADER)
+ {
+ task->protocol_flags |= RSPAMD_TASK_PROTOCOL_FLAG_MILTER;
+ msg_debug_protocol("read Milter header, value: %T", hv_tok);
+ }
+ break;
+ case 't':
+ case 'T':
+ IF_HEADER(TLS_CIPHER_HEADER)
+ {
+ task->flags |= RSPAMD_TASK_FLAG_SSL;
+ msg_debug_protocol("read TLS cipher header, value: %T", hv_tok);
+ }
+ break;
+ default:
+ msg_debug_protocol("generic header: %T", hn_tok);
+ break;
}
rspamd_task_add_request_header (task, hn_tok, hv_tok);
@@ -1716,6 +1716,7 @@ void rspamd_protocol_http_reply(struct rspamd_http_message *msg,
rspamd_fstring_free(reply);
rspamd_http_message_set_body_from_fstring_steal(msg, compressed_reply);
rspamd_http_message_add_header(msg, COMPRESSION_HEADER, "zstd");
+ rspamd_http_message_add_header(msg, CONTENT_ENCODING_HEADER, "zstd");
if (task->cfg->libs_ctx->out_dict &&
task->cfg->libs_ctx->out_dict->id != 0) {
diff --git a/src/libserver/protocol_internal.h b/src/libserver/protocol_internal.h
index 11f21430e..5582908c2 100644
--- a/src/libserver/protocol_internal.h
+++ b/src/libserver/protocol_internal.h
@@ -93,6 +93,8 @@ extern "C" {
#define RAW_DATA_HEADER "Raw"
#define COMPRESSION_HEADER "Compression"
#define MESSAGE_OFFSET_HEADER "Message-Offset"
+#define CONTENT_ENCODING_HEADER "Content-Encoding"
+#define ACCEPT_ENCODING_HEADER "Accept-Enconding"
#ifdef __cplusplus
}
diff --git a/src/libserver/ssl_util.c b/src/libserver/ssl_util.c
index b739961a8..c0443ecd9 100644
--- a/src/libserver/ssl_util.c
+++ b/src/libserver/ssl_util.c
@@ -1,11 +1,11 @@
-/*-
- * Copyright 2016 Vsevolod Stakhov
+/*
+ * Copyright 2024 Vsevolod Stakhov
*
* Licensed 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
+ * 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,
@@ -1054,6 +1054,9 @@ gpointer rspamd_init_ssl_ctx_noverify(void)
return ssl_ctx_noverify;
}
+#if defined(RSPAMD_LEGACY_SSL_PROVIDER) && OPENSSL_VERSION_NUMBER >= 0x30000000L
+#include <openssl/provider.h>
+#endif
void rspamd_openssl_maybe_init(void)
{
@@ -1075,6 +1078,16 @@ void rspamd_openssl_maybe_init(void)
#else
OPENSSL_init_ssl(0, NULL);
#endif
+#if defined(RSPAMD_LEGACY_SSL_PROVIDER) && OPENSSL_VERSION_NUMBER >= 0x30000000L
+ if (OSSL_PROVIDER_load(NULL, "legacy") == NULL) {
+ msg_err("cannot load legacy OpenSSL provider: %s", ERR_lib_error_string(ERR_get_error()));
+ ERR_clear_error();
+ }
+ if (OSSL_PROVIDER_load(NULL, "default") == NULL) {
+ msg_err("cannot load default OpenSSL provider: %s", ERR_lib_error_string(ERR_get_error()));
+ ERR_clear_error();
+ }
+#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
OPENSSL_config(NULL);
diff --git a/src/libserver/task.c b/src/libserver/task.c
index 833046470..bd1e07549 100644
--- a/src/libserver/task.c
+++ b/src/libserver/task.c
@@ -519,7 +519,11 @@ rspamd_task_load_message(struct rspamd_task *task,
debug_task("got input of length %z", task->msg.len);
/* Check compression */
- tok = rspamd_task_get_request_header(task, "compression");
+ tok = rspamd_task_get_request_header(task, COMPRESSION_HEADER);
+
+ if (!tok) {
+ tok = rspamd_task_get_request_header(task, CONTENT_ENCODING_HEADER);
+ }
if (tok) {
/* Need to uncompress */
diff --git a/src/libserver/worker_util.c b/src/libserver/worker_util.c
index 383d89c14..75836573f 100644
--- a/src/libserver/worker_util.c
+++ b/src/libserver/worker_util.c
@@ -57,6 +57,7 @@
#include "contrib/libev/ev.h"
#include "libstat/stat_api.h"
+#include "libserver/protocol_internal.h"
struct rspamd_worker *rspamd_current_worker = NULL;
@@ -600,7 +601,7 @@ rspamd_controller_maybe_compress(struct rspamd_http_connection_entry *entry,
{
if (entry->support_gzip) {
if (rspamd_fstring_gzip(&buf)) {
- rspamd_http_message_add_header(msg, "Content-Encoding", "gzip");
+ rspamd_http_message_add_header(msg, CONTENT_ENCODING_HEADER, "gzip");
}
}
diff --git a/src/libstat/stat_internal.h b/src/libstat/stat_internal.h
index 96d67cbf6..663c39df5 100644
--- a/src/libstat/stat_internal.h
+++ b/src/libstat/stat_internal.h
@@ -1,11 +1,11 @@
-/*-
- * Copyright 2016 Vsevolod Stakhov
+/*
+ * Copyright 2024 Vsevolod Stakhov
*
* Licensed 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
+ * 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,
@@ -41,8 +41,8 @@ struct rspamd_classifier {
GArray *statfiles_ids; /* int */
struct rspamd_stat_cache *cache;
gpointer cachecf;
- gulong spam_learns;
- gulong ham_learns;
+ guint64 spam_learns;
+ guint64 ham_learns;
int autolearn_cbref;
struct rspamd_classifier_config *cfg;
struct rspamd_stat_classifier *subrs;
diff --git a/src/libstat/stat_process.c b/src/libstat/stat_process.c
index 5db3af6ce..17caf4cc6 100644
--- a/src/libstat/stat_process.c
+++ b/src/libstat/stat_process.c
@@ -1017,6 +1017,9 @@ rspamd_stat_check_autolearn(struct rspamd_task *task)
cl = g_ptr_array_index(st_ctx->classifiers, i);
ret = FALSE;
+ rspamd_mempool_set_variable(task->task_pool, RSPAMD_MEMPOOL_HAM_LEARNS, (void *) &cl->ham_learns, NULL);
+ rspamd_mempool_set_variable(task->task_pool, RSPAMD_MEMPOOL_SPAM_LEARNS, (void *) &cl->spam_learns, NULL);
+
if (cl->cfg->opts) {
obj = ucl_object_lookup(cl->cfg->opts, "autolearn");
diff --git a/src/lua/lua_http.c b/src/lua/lua_http.c
index 8ba612c1b..904f1cbbf 100644
--- a/src/lua/lua_http.c
+++ b/src/lua/lua_http.c
@@ -21,6 +21,7 @@
#include "unix-std.h"
#include "zlib.h"
#include "utlist.h"
+#include "libserver/protocol_internal.h"
/***
* @module rspamd_http
@@ -1107,7 +1108,7 @@ lua_http_request(lua_State *L)
if (body) {
if (gzip) {
if (rspamd_fstring_gzip(&body)) {
- rspamd_http_message_add_header(msg, "Content-Encoding", "gzip");
+ rspamd_http_message_add_header(msg, CONTENT_ENCODING_HEADER, "gzip");
}
}
diff --git a/src/ragel/smtp_base.rl b/src/ragel/smtp_base.rl
index eefc430d5..952c3a5c3 100644
--- a/src/ragel/smtp_base.rl
+++ b/src/ragel/smtp_base.rl
@@ -1,5 +1,6 @@
%%{
machine smtp_base;
+ alphtype unsigned char;
# Base SMTP definitions
# Dependencies: none
diff --git a/src/rspamadm/CMakeLists.txt b/src/rspamadm/CMakeLists.txt
index 5e88ec8dd..2f32a95f5 100644
--- a/src/rspamadm/CMakeLists.txt
+++ b/src/rspamadm/CMakeLists.txt
@@ -22,9 +22,9 @@ ENDIF()
ADD_EXECUTABLE(rspamadm ${RSPAMADMSRC})
TARGET_LINK_LIBRARIES(rspamadm rspamd-server)
-IF (NOT DEBIAN_BUILD)
+IF (NOT NO_TARGET_VERSIONS)
SET_TARGET_PROPERTIES(rspamadm PROPERTIES VERSION ${RSPAMD_VERSION})
-ENDIF (NOT DEBIAN_BUILD)
+ENDIF ()
SET_TARGET_PROPERTIES(rspamadm PROPERTIES LINKER_LANGUAGE CXX)
ADD_BACKWARD(rspamadm)
diff --git a/src/rspamd.c b/src/rspamd.c
index b6c361cb2..6c204e266 100644
--- a/src/rspamd.c
+++ b/src/rspamd.c
@@ -1326,7 +1326,7 @@ version(struct rspamd_main *rspamd_main)
#ifndef __has_feature
#define __has_feature(x) 0
#endif
-#if (defined(__has_feature) && __has_feature(address_sanitizer)) || defined(ADDRESS_SANITIZER)
+#if (defined(__has_feature) && __has_feature(address_sanitizer)) || defined(ADDRESS_SANITIZER) || defined(__SANITIZE_ADDRESS__)
rspamd_printf("ASAN enabled: TRUE\n");
#else
rspamd_printf("ASAN enabled: FALSE\n");
diff --git a/src/rspamd_proxy.c b/src/rspamd_proxy.c
index dbdd2e5a7..e2a866178 100644
--- a/src/rspamd_proxy.c
+++ b/src/rspamd_proxy.c
@@ -38,6 +38,7 @@
#include "libmime/lang_detection.h"
#include <math.h>
+#include <string.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h> /* for TCP_NODELAY */
@@ -2205,7 +2206,7 @@ proxy_client_finish_handler(struct rspamd_http_connection *conn,
rspamd_http_message_remove_header(msg, "Connection");
rspamd_http_message_remove_header(msg, "Key");
rspamd_http_message_add_header_len(msg, LOG_TAG_HEADER, session->pool->tag.uid,
- sizeof(session->pool->tag.uid));
+ strnlen(session->pool->tag.uid, sizeof(session->pool->tag.uid)));
proxy_open_mirror_connections(session);
rspamd_http_connection_reset(session->client_conn);