Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/phpredis/phpredis.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml21
-rw-r--r--README.markdown33
-rw-r--r--cluster_library.c67
-rw-r--r--cluster_library.h4
-rw-r--r--common.h7
-rw-r--r--library.c686
-rw-r--r--library.h63
-rw-r--r--php_redis.h18
-rw-r--r--redis.c138
-rw-r--r--redis_array.c133
-rw-r--r--redis_array_impl.c223
-rw-r--r--redis_array_impl.h10
-rw-r--r--redis_cluster.c204
-rw-r--r--redis_cluster.h1
-rw-r--r--redis_commands.c41
-rw-r--r--redis_commands.h3
-rw-r--r--redis_session.c238
-rw-r--r--tests/RedisArrayTest.php4
-rw-r--r--tests/RedisClusterTest.php18
-rw-r--r--tests/RedisTest.php156
-rw-r--r--tests/TestRedis.php32
-rw-r--r--tests/TestSuite.php88
-rwxr-xr-xtests/make-cluster.sh105
-rw-r--r--tests/startSession.php2
-rw-r--r--tests/users.acl2
25 files changed, 1641 insertions, 656 deletions
diff --git a/.travis.yml b/.travis.yml
index 52296b1d..4520a6d8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -40,22 +40,23 @@ before_install:
- ./configure $CFGARGS
install: make install
before_script:
+ - sudo add-apt-repository ppa:chris-lea/redis-server -y && sudo apt-get update && sudo apt install redis-server
- mkdir -p tests/nodes/ && echo > tests/nodes/nodemap
- - redis-server --port 0 --daemonize yes --requirepass phpredis --unixsocket /tmp/redis.sock
- - for PORT in $(seq 6379 6382) $(seq 32767 32769); do redis-server --port $PORT --daemonize yes --requirepass phpredis; done
- - for PORT in $(seq 7000 7011); do redis-server --port $PORT --cluster-enabled yes --cluster-config-file $PORT.conf --daemonize yes --requirepass phpredis --masterauth phpredis; echo 127.0.0.1:$PORT >> tests/nodes/nodemap; done
+ - redis-server --port 0 --daemonize yes --aclfile tests/users.acl --unixsocket /tmp/redis.sock
+ - for PORT in $(seq 6379 6382) $(seq 32767 32769); do redis-server --port $PORT --daemonize yes --aclfile tests/users.acl; done
+ - for PORT in $(seq 7000 7011); do redis-server --port $PORT --cluster-enabled yes --cluster-config-file $PORT.conf --daemonize yes --aclfile tests/users.acl; echo 127.0.0.1:$PORT >> tests/nodes/nodemap; done
- for PORT in $(seq 26379 26380); do wget download.redis.io/redis-stable/sentinel.conf -O $PORT.conf; echo sentinel auth-pass mymaster phpredis >> $PORT.conf; redis-server $PORT.conf --port $PORT --daemonize yes --sentinel; done
- - echo yes | redis-cli --cluster create $(seq -f 127.0.0.1:%g 7000 7011) --cluster-replicas 3 -a phpredis
+ - echo yes | redis-cli --cluster create $(seq -f 127.0.0.1:%g 7000 7011) --cluster-replicas 3 --user phpredis -a phpredis
- echo 'extension = redis.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- openssl req -x509 -newkey rsa:1024 -nodes -keyout stunnel.key -out stunnel.pem -days 1 -subj '/CN=localhost'
- echo -e 'key=stunnel.key\ncert=stunnel.pem\npid=/tmp/stunnel.pid\n[redis]\naccept=6378\nconnect=6379' > stunnel.conf
- stunnel stunnel.conf
script:
- - php tests/TestRedis.php --class Redis --auth phpredis
- - php tests/TestRedis.php --class RedisArray --auth phpredis
- - php tests/TestRedis.php --class RedisCluster --auth phpredis
+ - php tests/TestRedis.php --class Redis --user phpredis --auth phpredis
+ - php tests/TestRedis.php --class RedisArray --user phpredis --auth phpredis
+ - php tests/TestRedis.php --class RedisCluster --user phpredis --auth phpredis
- php tests/TestRedis.php --class RedisSentinel --auth phpredis
- - USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class Redis --auth phpredis
- - USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisArray --auth phpredis
- - USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisCluster --auth phpredis
+ - USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class Redis --user phpredis --auth phpredis
+ - USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisArray --user phpredis --auth phpredis
+ - USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisCluster --user phpredis --auth phpredis
- USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisSentinel --auth phpredis
diff --git a/README.markdown b/README.markdown
index b2eda952..0cf5b97d 100644
--- a/README.markdown
+++ b/README.markdown
@@ -75,7 +75,7 @@ session.save_path = "tcp://host1:6379?weight=1, tcp://host2:6379?weight=2&timeou
* timeout (float): the connection timeout to a redis host, expressed in seconds. If the host is unreachable in that amount of time, the session storage will be unavailable for the client. The default timeout is very high (86400 seconds).
* persistent (integer, should be 1 or 0): defines if a persistent connection should be used. **(experimental setting)**
* prefix (string, defaults to "PHPREDIS_SESSION:"): used as a prefix to the Redis key in which the session is stored. The key is composed of the prefix followed by the session ID.
-* auth (string, empty by default): used to authenticate with the server prior to sending commands.
+* auth (string, or an array with one or two elements): used to authenticate with the server prior to sending commands.
* database (integer): selects a different database.
Sessions have a lifetime expressed in seconds and stored in the INI variable "session.gc_maxlifetime". You can change it with [`ini_set()`](http://php.net/ini_set).
@@ -270,18 +270,27 @@ $redis->pconnect('/tmp/redis.sock'); // unix domain socket - would be another co
### auth
-----
-_**Description**_: Authenticate the connection using a password.
+_**Description**_: Authenticate the connection using a password or a username and password.
*Warning*: The password is sent in plain-text over the network.
##### *Parameters*
-*STRING*: password
+*MIXED*: password
##### *Return value*
*BOOL*: `TRUE` if the connection is authenticated, `FALSE` otherwise.
+*Note*: In order to authenticate with a username and password you need Redis >= 6.0.
+
##### *Example*
~~~php
+/* Authenticate with the password 'foobared' */
$redis->auth('foobared');
+
+/* Authenticate with the username 'phpredis', and password 'haxx00r' */
+$redis->auth(['phpredis', 'haxx00r']);
+
+/* Authenticate with the password 'foobared' */
+$redis->auth(['foobared']);
~~~
### select
@@ -417,6 +426,7 @@ _**Description**_: Sends a string to Redis, which replies with the same string
## Server
+1. [acl](#acl) - Manage Redis ACLs
1. [bgRewriteAOF](#bgrewriteaof) - Asynchronously rewrite the append-only file
1. [bgSave](#bgsave) - Asynchronously save the dataset to disk (in background)
1. [config](#config) - Get or Set the Redis server configuration parameters
@@ -431,6 +441,23 @@ _**Description**_: Sends a string to Redis, which replies with the same string
1. [time](#time) - Return the current server time
1. [slowLog](#slowlog) - Access the Redis slowLog entries
+### acl
+-----
+_**Description**_: Execute the Redis ACL command.
+
+##### *Parameters*
+_variable_: Minumum of one argument for `Redis` and two for `RedisCluster`.
+
+##### *Example*
+~~~php
+$redis->acl('USERS'); /* Get a list of users */
+$redis->acl('LOG'); /* See log of Redis' ACL subsystem */
+~~~
+
+*Note*: In order to user the `ACL` command you must be communicating with Redis >= 6.0 and be logged into an account that has access to administration commands such as ACL. Please reference [this tutorial](https://redis.io/topics/acl) for an overview of Redis 6 ACLs and [the redis command reference](https://redis.io/commands) for every ACL subcommand.
+
+*Note*: If you are connecting to Redis server >= 4.0.0 you can remove a key with the `unlink` method in the exact same way you would use `del`. The Redis [unlink](https://redis.io/commands/unlink) command is non-blocking and will perform the actual deletion asynchronously.
+
### bgRewriteAOF
-----
_**Description**_: Start the background rewrite of AOF (Append-Only File)
diff --git a/cluster_library.c b/cluster_library.c
index 8d244a43..98ba9c2c 100644
--- a/cluster_library.c
+++ b/cluster_library.c
@@ -637,9 +637,7 @@ cluster_node_create(redisCluster *c, char *host, size_t host_len,
node->sock = redis_sock_create(host, host_len, port, c->timeout,
c->read_timeout, c->persistent, NULL, 0);
- if (c->flags->auth) {
- node->sock->auth = zend_string_copy(c->flags->auth);
- }
+ redis_sock_set_auth(node->sock, c->flags->user, c->flags->pass);
return node;
}
@@ -850,8 +848,8 @@ cluster_free(redisCluster *c, int free_ctx)
/* Free any allocated prefix */
if (c->flags->prefix) zend_string_release(c->flags->prefix);
- /* Free auth info we've got */
- if (c->flags->auth) zend_string_release(c->flags->auth);
+
+ redis_sock_free_auth(c->flags);
efree(c->flags);
/* Call hash table destructors */
@@ -1050,10 +1048,8 @@ cluster_init_seeds(redisCluster *cluster, zend_string **seeds, uint32_t nseeds)
(unsigned short)atoi(sep+1), cluster->timeout,
cluster->read_timeout, cluster->persistent, NULL, 0);
- // Set auth information if specified
- if (cluster->flags->auth) {
- sock->auth = zend_string_copy(cluster->flags->auth);
- }
+ /* Credentials */
+ redis_sock_set_auth(sock, cluster->flags->user, cluster->flags->pass);
// Index this seed by host/port
key_len = snprintf(key, sizeof(key), "%s:%u", ZSTR_VAL(sock->host),
@@ -2294,11 +2290,40 @@ cluster_xinfo_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx)
add_next_index_zval(&c->multi_resp, &z_ret);
}
+static void
+cluster_acl_custom_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx,
+ int (*cb)(RedisSock*, zval*, long))
+{
+ zval z_ret;
+
+ array_init(&z_ret);
+ if (cb(c->cmd_sock, &z_ret, c->reply_len) != SUCCESS) {
+ zval_dtor(&z_ret);
+ CLUSTER_RETURN_FALSE(c);
+ }
+
+ if (CLUSTER_IS_ATOMIC(c)) {
+ RETURN_ZVAL(&z_ret, 0, 1);
+ }
+ add_next_index_zval(&c->multi_resp, &z_ret);
+}
+
+PHP_REDIS_API void
+cluster_acl_getuser_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) {
+ cluster_acl_custom_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx, redis_read_acl_getuser_reply);
+}
+
+PHP_REDIS_API void
+cluster_acl_log_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) {
+ cluster_acl_custom_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx, redis_read_acl_log_reply);
+}
+
/* MULTI BULK response loop where we might pull the next one */
PHP_REDIS_API zval *cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, int pull, mbulk_cb cb, zval *z_ret)
{
ZVAL_NULL(z_ret);
+
// Pull our next response if directed
if (pull) {
if (cluster_check_response(c, &c->reply_type) < 0)
@@ -2712,7 +2737,7 @@ void free_seed_array(zend_string **seeds, uint32_t nseeds) {
if (seeds == NULL)
return;
- for (i = 0; i < nseeds; i++)
+ for (i = 0; i < nseeds; i++)
zend_string_release(seeds[i]);
efree(seeds);
@@ -2771,11 +2796,11 @@ cleanup:
/* Validate cluster construction arguments and return a sanitized and validated
* array of seeds */
zend_string**
-cluster_validate_args(double timeout, double read_timeout, HashTable *seeds,
- uint32_t *nseeds, char **errstr)
+cluster_validate_args(double timeout, double read_timeout, HashTable *seeds,
+ uint32_t *nseeds, char **errstr)
{
zend_string **retval;
-
+
if (timeout < 0L || timeout > INT_MAX) {
if (errstr) *errstr = "Invalid timeout";
return NULL;
@@ -2862,21 +2887,9 @@ PHP_REDIS_API redisCachedCluster *cluster_cache_load(zend_string *hash) {
/* Cache a cluster's slot information in persistent_list if it's enabled */
PHP_REDIS_API int cluster_cache_store(zend_string *hash, HashTable *nodes) {
- redisCachedCluster *cc;
-
- /* Construct our cache */
- cc = cluster_cache_create(hash, nodes);
-
- /* Set up our resource */
-#if PHP_VERSION_ID < 70300
- zend_resource le;
- le.type = le_cluster_slot_cache;
- le.ptr = cc;
+ redisCachedCluster *cc = cluster_cache_create(hash, nodes);
- zend_hash_update_mem(&EG(persistent_list), cc->hash, (void*)&le, sizeof(zend_resource));
-#else
- zend_register_persistent_resource_ex(cc->hash, cc, le_cluster_slot_cache);
-#endif
+ redis_register_persistent_resource(cc->hash, cc, le_cluster_slot_cache);
return SUCCESS;
}
diff --git a/cluster_library.h b/cluster_library.h
index 062db790..de9d1718 100644
--- a/cluster_library.h
+++ b/cluster_library.h
@@ -498,6 +498,10 @@ PHP_REDIS_API void cluster_xclaim_resp(INTERNAL_FUNCTION_PARAMETERS,
PHP_REDIS_API void cluster_xinfo_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx);
+/* Custom ACL handlers */
+PHP_REDIS_API void cluster_acl_getuser_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx);
+PHP_REDIS_API void cluster_acl_log_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx);
+
/* MULTI BULK processing callbacks */
int mbulk_resp_loop(RedisSock *redis_sock, zval *z_result,
long long count, void *ctx);
diff --git a/common.h b/common.h
index 14aefb53..dda14366 100644
--- a/common.h
+++ b/common.h
@@ -210,6 +210,10 @@ typedef enum {
REDIS_PROCESS_RESPONSE_CLOSURE(resp_func, ctx) \
}
+/* Case sensitive compare against compile-time static string */
+#define REDIS_STRCMP_STATIC(s, len, sstr) \
+ (len == sizeof(sstr) - 1 && !strncmp(s, sstr, len))
+
/* Case insensitive compare against compile-time static string */
#define REDIS_STRICMP_STATIC(s, len, sstr) \
(len == sizeof(sstr) - 1 && !strncasecmp(s, sstr, len))
@@ -263,7 +267,8 @@ typedef struct {
php_stream_context *stream_ctx;
zend_string *host;
int port;
- zend_string *auth;
+ zend_string *user;
+ zend_string *pass;
double timeout;
double read_timeout;
long retry_interval;
diff --git a/library.c b/library.c
index d9fc53a5..209cd1d0 100644
--- a/library.c
+++ b/library.c
@@ -1,3 +1,5 @@
+#include "php_redis.h"
+
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
@@ -55,6 +57,7 @@
#endif
#include <ext/standard/php_rand.h>
+#include <ext/hash/php_hash.h>
#define UNSERIALIZE_NONE 0
#define UNSERIALIZE_KEYS 1
@@ -77,28 +80,44 @@ extern zend_class_entry *redis_exception_ce;
extern int le_redis_pconnect;
+static int redis_mbulk_reply_zipped_raw_variant(RedisSock *redis_sock, zval *zret, int count);
+
+/* Register a persistent resource in a a way that works for every PHP 7 version. */
+void redis_register_persistent_resource(zend_string *id, void *ptr, int le_id) {
+#if PHP_VERSION_ID < 70300
+ zend_resource res;
+ res.type = le_id;
+ res.ptr = ptr;
+
+ zend_hash_str_update_mem(&EG(persistent_list), ZSTR_VAL(id), ZSTR_LEN(id), &res, sizeof(res));
+#else
+ zend_register_persistent_resource(ZSTR_VAL(id), ZSTR_LEN(id), ptr, le_id);
+#endif
+}
+
static ConnectionPool *
redis_sock_get_connection_pool(RedisSock *redis_sock)
{
- ConnectionPool *p;
- zend_string *persistent_id = strpprintf(0, "phpredis_%s:%d", ZSTR_VAL(redis_sock->host), redis_sock->port);
+ ConnectionPool *pool;
zend_resource *le;
+ zend_string *persistent_id;
- if ((le = zend_hash_find_ptr(&EG(persistent_list), persistent_id)) == NULL) {
- p = pecalloc(1, sizeof(*p), 1);
- zend_llist_init(&p->list, sizeof(php_stream *), NULL, 1);
-#if (PHP_VERSION_ID < 70300)
- zend_resource res;
- res.type = le_redis_pconnect;
- res.ptr = p;
- le = &res;
- zend_hash_str_update_mem(&EG(persistent_list), ZSTR_VAL(persistent_id), ZSTR_LEN(persistent_id), le, sizeof(*le));
-#else
- le = zend_register_persistent_resource(ZSTR_VAL(persistent_id), ZSTR_LEN(persistent_id), p, le_redis_pconnect);
-#endif
+ /* Generate our unique pool id depending on configuration */
+ persistent_id = redis_pool_spprintf(redis_sock, INI_STR("redis.pconnect.pool_pattern"));
+
+ /* Return early if we can find the pool */
+ if ((le = zend_hash_find_ptr(&EG(persistent_list), persistent_id))) {
+ zend_string_release(persistent_id);
+ return le->ptr;
}
+
+ /* Create the pool and store it in our persistent list */
+ pool = pecalloc(1, sizeof(*pool), 1);
+ zend_llist_init(&pool->list, sizeof(php_stream *), NULL, 1);
+ redis_register_persistent_resource(persistent_id, pool, le_redis_pconnect);
+
zend_string_release(persistent_id);
- return le->ptr;
+ return pool;
}
/* Helper to reselect the proper DB number when we reconnect */
@@ -129,34 +148,112 @@ static int reselect_db(RedisSock *redis_sock) {
return 0;
}
-/* Helper to resend AUTH <password> in the case of a reconnect */
-PHP_REDIS_API int
-redis_sock_auth(RedisSock *redis_sock)
+/* Attempt to read a single +OK response */
+static int redis_sock_read_ok(RedisSock *redis_sock) {
+ char buf[64];
+ size_t len;
+
+ if (redis_sock_read_single_line(redis_sock, buf, sizeof(buf), &len, 0) < 0)
+ return FAILURE;
+
+ return REDIS_STRCMP_STATIC(buf, len, "OK") ? SUCCESS : FAILURE;
+}
+
+/* Append an AUTH command to a smart string if neccessary. This will either
+ * append the new style AUTH <user> <password>, old style AUTH <password>, or
+ * append no command at all. Function returns 1 if we appended a command
+ * and 0 otherwise. */
+static int redis_sock_append_auth(RedisSock *redis_sock, smart_string *str) {
+ /* We need a password at least */
+ if (redis_sock->pass == NULL)
+ return 0;
+
+ REDIS_CMD_INIT_SSTR_STATIC(str, !!redis_sock->user + !!redis_sock->pass, "AUTH");
+
+ if (redis_sock->user)
+ redis_cmd_append_sstr_zstr(str, redis_sock->user);
+
+ redis_cmd_append_sstr_zstr(str, redis_sock->pass);
+
+ /* We appended a command */
+ return 1;
+}
+
+PHP_REDIS_API void
+redis_sock_copy_auth(RedisSock *dst, RedisSock *src) {
+ redis_sock_set_auth(dst, src->user, src->pass);
+}
+
+PHP_REDIS_API void
+redis_sock_set_auth(RedisSock *redis_sock, zend_string *user, zend_string *pass)
{
- char *cmd, *response;
- int cmd_len, response_len;
+ /* Release existing user/pass */
+ redis_sock_free_auth(redis_sock);
- cmd_len = redis_spprintf(redis_sock, NULL, &cmd, "AUTH", "S", redis_sock->auth);
+ /* Set new user/pass */
+ redis_sock->user = user ? zend_string_copy(user) : NULL;
+ redis_sock->pass = pass ? zend_string_copy(pass) : NULL;
+}
- if (redis_sock_write(redis_sock, cmd, cmd_len) < 0) {
- efree(cmd);
- return -1;
- }
- efree(cmd);
+PHP_REDIS_API void
+redis_sock_set_auth_zval(RedisSock *redis_sock, zval *zv) {
+ zend_string *user, *pass;
- response = redis_sock_read(redis_sock, &response_len);
- if (response == NULL) {
- return -1;
+ if (redis_extract_auth_info(zv, &user, &pass) == FAILURE)
+ return;
+
+ redis_sock_set_auth(redis_sock, user, pass);
+
+ if (user) zend_string_release(user);
+ if (pass) zend_string_release(pass);
+}
+
+PHP_REDIS_API void
+redis_sock_free_auth(RedisSock *redis_sock) {
+ if (redis_sock->user) {
+ zend_string_release(redis_sock->user);
+ redis_sock->user = NULL;
}
- if (strncmp(response, "+OK", 3)) {
- efree(response);
- return -1;
+ if (redis_sock->pass) {
+ zend_string_release(redis_sock->pass);
+ redis_sock->pass = NULL;
}
+}
- efree(response);
- return 0;
+PHP_REDIS_API char *
+redis_sock_auth_cmd(RedisSock *redis_sock, int *cmdlen) {
+ char *cmd;
+
+ /* AUTH requires at least a password */
+ if (redis_sock->pass == NULL)
+ return NULL;
+
+ if (redis_sock->user) {
+ *cmdlen = redis_spprintf(redis_sock, NULL, &cmd, "AUTH", "SS", redis_sock->user, redis_sock->pass);
+ } else {
+ *cmdlen = redis_spprintf(redis_sock, NULL, &cmd, "AUTH", "S", redis_sock->pass);
+ }
+
+ return cmd;
+}
+
+/* Send Redis AUTH and process response */
+PHP_REDIS_API int redis_sock_auth(RedisSock *redis_sock) {
+ char *cmd;
+ int cmdlen, rv = FAILURE;
+
+ if ((cmd = redis_sock_auth_cmd(redis_sock, &cmdlen)) == NULL)
+ return SUCCESS;
+
+ if (redis_sock_write(redis_sock, cmd, cmdlen) < 0)
+ goto cleanup;
+
+ rv = redis_sock_read_ok(redis_sock) == SUCCESS ? SUCCESS : FAILURE;
+cleanup:
+ efree(cmd);
+ return rv;
}
/* Helper function and macro to test a RedisSock error prefix. */
@@ -180,18 +277,24 @@ redis_error_throw(RedisSock *redis_sock)
if (redis_sock == NULL || redis_sock->err == NULL)
return;
+ /* Redis 6 decided to add 'ERR AUTH' which has a normal 'ERR' prefix
+ * but is actually an authentication error that we will want to throw
+ * an exception for, so just short circuit if this is any other 'ERR'
+ * prefixed error. */
+ if (REDIS_SOCK_ERRCMP_STATIC(redis_sock, "ERR") &&
+ !REDIS_SOCK_ERRCMP_STATIC(redis_sock, "ERR AUTH")) return;
+
/* We may want to flip this logic and check for MASTERDOWN, AUTH,
* and LOADING but that may have side effects (esp for things like
* Disque) */
- if (!REDIS_SOCK_ERRCMP_STATIC(redis_sock, "ERR") &&
- !REDIS_SOCK_ERRCMP_STATIC(redis_sock, "NOSCRIPT") &&
+ if (!REDIS_SOCK_ERRCMP_STATIC(redis_sock, "NOSCRIPT") &&
!REDIS_SOCK_ERRCMP_STATIC(redis_sock, "NOQUORUM") &&
!REDIS_SOCK_ERRCMP_STATIC(redis_sock, "NOGOODSLAVE") &&
!REDIS_SOCK_ERRCMP_STATIC(redis_sock, "WRONGTYPE") &&
!REDIS_SOCK_ERRCMP_STATIC(redis_sock, "BUSYGROUP") &&
!REDIS_SOCK_ERRCMP_STATIC(redis_sock, "NOGROUP"))
{
- REDIS_THROW_EXCEPTION( ZSTR_VAL(redis_sock->err), 0);
+ REDIS_THROW_EXCEPTION(ZSTR_VAL(redis_sock->err), 0);
}
}
@@ -247,11 +350,11 @@ redis_check_eof(RedisSock *redis_sock, int no_throw)
/* check for EOF again. */
errno = 0;
if (php_stream_eof(redis_sock->stream) == 0) {
- /* If we're using a password, attempt a reauthorization */
- if (redis_sock->auth && redis_sock_auth(redis_sock) != 0) {
+ if (redis_sock_auth(redis_sock) != SUCCESS) {
errmsg = "AUTH failed while reconnecting";
break;
}
+
redis_sock->status = REDIS_SOCK_STATUS_READY;
/* If we're using a non-zero db, reselect it */
if (redis_sock->dbNumber && reselect_db(redis_sock) != 0) {
@@ -583,6 +686,19 @@ redis_sock_read(RedisSock *redis_sock, int *buf_len)
return NULL;
}
+static int redis_sock_read_cmp(RedisSock *redis_sock, const char *cmp, int cmplen) {
+ char *resp;
+ int len, rv = FAILURE;
+
+ if ((resp = redis_sock_read(redis_sock, &len)) == NULL)
+ return FAILURE;
+
+ rv = len == cmplen && !memcmp(resp, cmp, cmplen) ? SUCCESS : FAILURE;
+
+ efree(resp);
+ return rv;
+}
+
/* A simple union to store the various arg types we might handle in our
* redis_spprintf command formatting function */
union resparg {
@@ -594,6 +710,107 @@ union resparg {
double dval;
};
+static zend_string *redis_hash_auth(zend_string *user, zend_string *pass) {
+ zend_string *algo, *hex;
+ smart_str salted = {0};
+ const php_hash_ops *ops;
+ unsigned char *digest;
+ void *ctx;
+
+ /* No op if there is not username/password */
+ if (user == NULL && pass == NULL)
+ return NULL;
+
+ /* Theoretically inpossible but check anyway */
+ algo = zend_string_init("sha256", sizeof("sha256") - 1, 0);
+ if ((ops = redis_hash_fetch_ops(algo)) == NULL) {
+ zend_string_release(algo);
+ return NULL;
+ }
+
+ /* Hash username + password with our salt global */
+ smart_str_alloc(&salted, 256, 0);
+ if (user) smart_str_append_ex(&salted, user, 0);
+ if (pass) smart_str_append_ex(&salted, pass, 0);
+ smart_str_appendl_ex(&salted, REDIS_G(salt), sizeof(REDIS_G(salt)), 0);
+
+ ctx = emalloc(ops->context_size);
+ ops->hash_init(ctx);
+ ops->hash_update(ctx, (const unsigned char *)ZSTR_VAL(salted.s), ZSTR_LEN(salted.s));
+
+ digest = emalloc(ops->digest_size);
+ ops->hash_final(digest, ctx);
+ efree(ctx);
+
+ hex = zend_string_safe_alloc(ops->digest_size, 2, 0, 0);
+ php_hash_bin2hex(ZSTR_VAL(hex), digest, ops->digest_size);
+ ZSTR_VAL(hex)[2 * ops->digest_size] = 0;
+
+ efree(digest);
+ zend_string_release(algo);
+ smart_str_free(&salted);
+
+ return hex;
+}
+
+static void append_auth_hash(smart_str *dst, zend_string *user, zend_string *pass) {
+ zend_string *s;
+
+ if ((s = redis_hash_auth(user, pass)) != NULL) {
+ smart_str_appendc(dst, ':');
+ smart_str_append_ex(dst, s, 0);
+ zend_string_release(s);
+ }
+}
+
+/* A printf like function to generate our connection pool hash value. */
+PHP_REDIS_API zend_string *
+redis_pool_spprintf(RedisSock *redis_sock, char *fmt, ...) {
+ smart_str str = {0};
+
+ smart_str_alloc(&str, 128, 0);
+
+ /* We always include phpredis_<host>:<port> */
+ smart_str_appendl(&str, "phpredis_", sizeof("phpredis_") - 1);
+ smart_str_append_ex(&str, redis_sock->host, 0);
+ smart_str_appendc(&str, ':');
+ smart_str_append_long(&str, (zend_long)redis_sock->port);
+
+ /* Short circuit if we don't have a pattern */
+ if (fmt == NULL)
+ return str.s;
+
+ while (*fmt) {
+ switch (*fmt) {
+ case 'i':
+ if (redis_sock->persistent_id) {
+ smart_str_appendc(&str, ':');
+ smart_str_append_ex(&str, redis_sock->persistent_id, 0);
+ }
+ break;
+ case 'u':
+ smart_str_appendc(&str, ':');
+ if (redis_sock->user) {
+ smart_str_append_ex(&str, redis_sock->user, 0);
+ }
+ break;
+ case 'p':
+ append_auth_hash(&str, NULL, redis_sock->pass);
+ break;
+ case 'a':
+ append_auth_hash(&str, redis_sock->user, redis_sock->pass);
+ break;
+ default:
+ /* Maybe issue a php_error_docref? */
+ break;
+ }
+
+ fmt++;
+ }
+
+ return str.s;
+}
+
/* A printf like method to construct a Redis RESP command. It has been extended
* to take a few different format specifiers that are convenient to phpredis.
*
@@ -765,6 +982,10 @@ int redis_cmd_append_sstr_zval(smart_string *str, zval *z, RedisSock *redis_sock
return retval;
}
+int redis_cmd_append_sstr_zstr(smart_string *str, zend_string *zstr) {
+ return redis_cmd_append_sstr(str, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
+}
+
/* Append a string key to a redis command. This function takes care of prefixing the key
* for the caller and setting the slot argument if it is passed non null */
int redis_cmd_append_sstr_key(smart_string *str, char *key, size_t len, RedisSock *redis_sock, short *slot) {
@@ -1575,6 +1796,98 @@ redis_xinfo_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_t
return FAILURE;
}
+PHP_REDIS_API int
+redis_read_acl_log_reply(RedisSock *redis_sock, zval *zret, long count) {
+ zval zsub;
+ int i, nsub;
+
+ for (i = 0; i < count; i++) {
+ if (read_mbulk_header(redis_sock, &nsub) < 0 || nsub % 2 != 0)
+ return FAILURE;
+
+ array_init(&zsub);
+ if (redis_mbulk_reply_zipped_raw_variant(redis_sock, &zsub, nsub) == FAILURE)
+ return FAILURE;
+
+ add_next_index_zval(zret, &zsub);
+ }
+
+ return SUCCESS;
+}
+
+PHP_REDIS_API int
+redis_read_acl_getuser_reply(RedisSock *redis_sock, zval *zret, long count) {
+ REDIS_REPLY_TYPE type;
+ zval zv;
+ char *key, *val;
+ long vlen;
+ int klen, i;
+
+ for (i = 0; i < count; i += 2) {
+ if (!(key = redis_sock_read(redis_sock, &klen)) ||
+ redis_read_reply_type(redis_sock, &type, &vlen) < 0 ||
+ (type != TYPE_BULK && type != TYPE_MULTIBULK) ||
+ vlen > INT_MAX)
+ {
+ if (key) efree(key);
+ return FAILURE;
+ }
+
+ if (type == TYPE_BULK) {
+ if (!(val = redis_sock_read_bulk_reply(redis_sock, (int)vlen)))
+ return FAILURE;
+ add_assoc_stringl_ex(zret, key, klen, val, vlen);
+ efree(val);
+ } else {
+ array_init(&zv);
+ redis_mbulk_reply_loop(redis_sock, &zv, (int)vlen, UNSERIALIZE_NONE);
+ add_assoc_zval_ex(zret, key, klen, &zv);
+ }
+
+ efree(key);
+ }
+
+ return SUCCESS;
+}
+
+int redis_acl_custom_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx,
+ int (*cb)(RedisSock*, zval*, long)) {
+ REDIS_REPLY_TYPE type;
+ int res = FAILURE;
+ zval zret;
+ long len;
+
+ if (redis_read_reply_type(redis_sock, &type, &len) == 0 && type == TYPE_MULTIBULK) {
+ array_init(&zret);
+
+ res = cb(redis_sock, &zret, len);
+ if (res == FAILURE) {
+ zval_dtor(&zret);
+ ZVAL_FALSE(&zret);
+ }
+ } else {
+ ZVAL_FALSE(&zret);
+ }
+
+ if (IS_ATOMIC(redis_sock)) {
+ RETVAL_ZVAL(&zret, 0, 0);
+ } else {
+ add_next_index_zval(z_tab, &zret);
+ }
+
+ return res;
+}
+
+int redis_acl_getuser_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
+ return redis_acl_custom_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, ctx,
+ redis_read_acl_getuser_reply);
+}
+
+int redis_acl_log_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
+ return redis_acl_custom_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, ctx,
+ redis_read_acl_log_reply);
+}
+
/* Zipped key => value reply but we don't touch anything (e.g. CONFIG GET) */
PHP_REDIS_API int redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
{
@@ -1776,15 +2089,11 @@ redis_sock_create(char *host, int host_len, int port,
{
RedisSock *redis_sock;
- redis_sock = ecalloc(1, sizeof(RedisSock));
+ redis_sock = ecalloc(1, sizeof(RedisSock));
redis_sock->host = zend_string_init(host, host_len, 0);
- redis_sock->stream = NULL;
redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED;
- redis_sock->watching = 0;
- redis_sock->dbNumber = 0;
redis_sock->retry_interval = retry_interval * 1000;
redis_sock->persistent = persistent;
- redis_sock->persistent_id = NULL;
if (persistent && persistent_id != NULL) {
redis_sock->persistent_id = zend_string_init(persistent_id, strlen(persistent_id), 0);
@@ -1796,67 +2105,41 @@ redis_sock_create(char *host, int host_len, int port,
redis_sock->serializer = REDIS_SERIALIZER_NONE;
redis_sock->compression = REDIS_COMPRESSION_NONE;
- redis_sock->compression_level = 0; /* default */
redis_sock->mode = ATOMIC;
- redis_sock->head = NULL;
- redis_sock->current = NULL;
-
- redis_sock->pipeline_cmd = NULL;
-
- redis_sock->err = NULL;
- redis_sock->scan = 0;
+ return redis_sock;
+}
- redis_sock->readonly = 0;
- redis_sock->tcp_keepalive = 0;
- redis_sock->reply_literal = 0;
+static int redis_uniqid(char *buf, size_t buflen) {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
- return redis_sock;
+ return snprintf(buf, buflen, "phpredis:%08lx%05lx:%08lx",
+ (long)tv.tv_sec, (long)tv.tv_usec, (long)php_rand());
}
static int
redis_sock_check_liveness(RedisSock *redis_sock)
{
- char inbuf[4096], uniqid[64];
- int uniqid_len;
+ char id[64], ok;
+ int idlen, auth;
smart_string cmd = {0};
- struct timeval tv;
- size_t len;
- if (redis_sock->auth) {
- redis_cmd_init_sstr(&cmd, 1, "AUTH", sizeof("AUTH") - 1);
- redis_cmd_append_sstr(&cmd, ZSTR_VAL(redis_sock->auth), ZSTR_LEN(redis_sock->auth));
- }
+ /* AUTH (if we need it) */
+ auth = redis_sock_append_auth(redis_sock, &cmd);
- gettimeofday(&tv, NULL);
- uniqid_len = snprintf(uniqid, sizeof(uniqid), "phpredis_pool:%08lx%05lx:%08lx", (long)tv.tv_sec, (long)tv.tv_usec, (long)php_rand());
- redis_cmd_init_sstr(&cmd, 1, "ECHO", sizeof("ECHO") - 1);
- redis_cmd_append_sstr(&cmd, uniqid, uniqid_len);
- smart_string_0(&cmd);
+ /* ECHO challenge/response */
+ idlen = redis_uniqid(id, sizeof(id));
+ REDIS_CMD_INIT_SSTR_STATIC(&cmd, 1, "ECHO");
+ redis_cmd_append_sstr(&cmd, id, idlen);
- if (redis_sock_write(redis_sock, cmd.c, cmd.len) < 0) {
- smart_string_free(&cmd);
- return FAILURE;
- }
- smart_string_free(&cmd);
+ /* Send command(s) and make sure we can consume reply(ies) */
+ ok = (redis_sock_write(redis_sock, cmd.c, cmd.len) >= 0) &&
+ (!auth || redis_sock_read_ok(redis_sock) == SUCCESS) &&
+ (redis_sock_read_cmp(redis_sock, id, idlen) == SUCCESS);
- if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
- return FAILURE;
- } else if (redis_sock->auth) {
- if (strncmp(inbuf, "+OK", 3) != 0) {
- return FAILURE;
- } else if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
- return FAILURE;
- }
- }
- if (*inbuf != TYPE_BULK ||
- atoi(inbuf + 1) != uniqid_len ||
- redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0 ||
- strncmp(inbuf, uniqid, uniqid_len) != 0
- ) {
- return FAILURE;
- }
- return SUCCESS;
+ smart_string_free(&cmd);
+ return ok ? SUCCESS : FAILURE;
}
/**
@@ -1999,9 +2282,8 @@ redis_sock_server_open(RedisSock *redis_sock)
}
// fall through
case REDIS_SOCK_STATUS_CONNECTED:
- if (redis_sock->auth && redis_sock_auth(redis_sock) != SUCCESS) {
+ if (redis_sock_auth(redis_sock) != SUCCESS)
break;
- }
redis_sock->status = REDIS_SOCK_STATUS_READY;
// fall through
case REDIS_SOCK_STATUS_READY:
@@ -2069,11 +2351,12 @@ redis_sock_set_stream_context(RedisSock *redis_sock, zval *options)
zend_string *zkey;
zval *z_ele;
- if (!redis_sock || Z_TYPE_P(options) != IS_ARRAY) {
+ if (!redis_sock || Z_TYPE_P(options) != IS_ARRAY)
return FAILURE;
- } else if (!redis_sock->stream_ctx) {
+
+ if (!redis_sock->stream_ctx)
redis_sock->stream_ctx = php_stream_context_alloc();
- }
+
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(options), zkey, z_ele) {
php_stream_context_set_option(redis_sock->stream_ctx, "ssl", ZSTR_VAL(zkey), z_ele);
} ZEND_HASH_FOREACH_END();
@@ -2159,7 +2442,7 @@ redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval
} else {
add_next_index_zval(z_tab, &z_multi_result);
}
- /*zval_copy_ctor(return_value); */
+
return 0;
}
@@ -2195,6 +2478,55 @@ redis_mbulk_reply_loop(RedisSock *redis_sock, zval *z_tab, int count,
}
}
+static int
+redis_mbulk_reply_zipped_raw_variant(RedisSock *redis_sock, zval *zret, int count) {
+ REDIS_REPLY_TYPE type;
+ char *key, *val;
+ int keylen, i;
+ zend_long lval;
+ double dval;
+ long vallen;
+
+ for (i = 0; i < count; i+= 2) {
+ /* Keys should always be bulk strings */
+ if ((key = redis_sock_read(redis_sock, &keylen)) == NULL)
+ return FAILURE;
+
+ /* This can vary */
+ if (redis_read_reply_type(redis_sock, &type, &vallen) < 0)
+ return FAILURE;
+
+ if (type == TYPE_BULK) {
+ if (vallen > INT_MAX || (val = redis_sock_read_bulk_reply(redis_sock, (int)vallen)) == NULL) {
+ efree(key);
+ return FAILURE;
+ }
+
+ /* Possibly overkill, but provides really nice types */
+ switch (is_numeric_string(val, vallen, &lval, &dval, 0)) {
+ case IS_LONG:
+ add_assoc_long_ex(zret, key, keylen, lval);
+ break;
+ case IS_DOUBLE:
+ add_assoc_double_ex(zret, key, keylen, dval);
+ break;
+ default:
+ add_assoc_stringl_ex(zret, key, keylen, val, vallen);
+ }
+
+ efree(val);
+ } else if (type == TYPE_INT) {
+ add_assoc_long_ex(zret, key, keylen, (zend_long)vallen);
+ } else {
+ add_assoc_null_ex(zret, key, keylen);
+ }
+
+ efree(key);
+ }
+
+ return SUCCESS;
+}
+
/* Specialized multibulk processing for HMGET where we need to pair requested
* keys with their returned values */
PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
@@ -2288,15 +2620,13 @@ PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock)
if (redis_sock->err) {
zend_string_release(redis_sock->err);
}
- if (redis_sock->auth) {
- zend_string_release(redis_sock->auth);
- }
if (redis_sock->persistent_id) {
zend_string_release(redis_sock->persistent_id);
}
if (redis_sock->host) {
zend_string_release(redis_sock->host);
}
+ redis_sock_free_auth(redis_sock);
efree(redis_sock);
}
@@ -2909,8 +3239,7 @@ variant_reply_generic(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
zval z_ret;
// Attempt to read our header
- if(redis_read_reply_type(redis_sock,&reply_type,&reply_info) < 0)
- {
+ if(redis_read_reply_type(redis_sock,&reply_type,&reply_info) < 0) {
return -1;
}
@@ -2975,4 +3304,157 @@ redis_read_variant_reply_strings(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_
return variant_reply_generic(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, 1, z_tab, ctx);
}
+PHP_REDIS_API
+int redis_extract_auth_info(zval *ztest, zend_string **user, zend_string **pass) {
+ zval *zv;
+ HashTable *ht;
+ int num;
+
+ /* The user may wish to send us something like [NULL, 'password'] or
+ * [false, 'password'] so don't convert NULL or FALSE into "". */
+ #define TRY_SET_AUTH_ARG(zv, ppzstr) \
+ do { \
+ if (Z_TYPE_P(zv) != IS_NULL && Z_TYPE_P(zv) != IS_FALSE) { \
+ *(ppzstr) = zval_get_string(zv); \
+ } \
+ } while (0)
+
+ /* Null out user and password */
+ *user = *pass = NULL;
+
+ /* User passed nothing */
+ if (ztest == NULL)
+ return SUCCESS;
+
+ /* Handle a non-array first */
+ if (Z_TYPE_P(ztest) != IS_ARRAY) {
+ *pass = zval_get_string(ztest);
+ return SUCCESS;
+ }
+
+ /* Handle the array case */
+ ht = Z_ARRVAL_P(ztest);
+ num = zend_hash_num_elements(ht);
+
+ /* Something other than one or two entries makes no sense */
+ if (num != 1 && num != 2) {
+ php_error_docref(NULL, E_WARNING, "When passing an array as auth it must have one or two elements!");
+ return FAILURE;
+ }
+
+ if (num == 2) {
+ if ((zv = REDIS_HASH_STR_FIND_STATIC(ht, "user")) ||
+ (zv = zend_hash_index_find(ht, 0)))
+ {
+ TRY_SET_AUTH_ARG(zv, user);
+ }
+
+ if ((zv = REDIS_HASH_STR_FIND_STATIC(ht, "pass")) ||
+ (zv = zend_hash_index_find(ht, 1)))
+ {
+ TRY_SET_AUTH_ARG(zv, pass);
+ }
+ } else if ((zv = REDIS_HASH_STR_FIND_STATIC(ht, "pass")) ||
+ (zv = zend_hash_index_find(ht, 0)))
+ {
+ TRY_SET_AUTH_ARG(zv, pass);
+ }
+
+ /* If we at least have a password, we're good */
+ if (*pass != NULL)
+ return SUCCESS;
+
+ /* Failure, clean everything up so caller doesn't need to care */
+ if (*user) zend_string_release(*user);
+ *user = NULL;
+
+ return FAILURE;
+}
+
+/* Helper methods to extract configuration settings from a hash table */
+
+zval *redis_hash_str_find_type(HashTable *ht, const char *key, int keylen, int type) {
+ zval *zv = zend_hash_str_find(ht, key, keylen);
+ if (zv == NULL || Z_TYPE_P(zv) != type)
+ return NULL;
+
+ return zv;
+}
+
+void redis_conf_double(HashTable *ht, const char *key, int keylen, double *dval) {
+ zval *zv = zend_hash_str_find(ht, key, keylen);
+ if (zv == NULL)
+ return;
+
+ *dval = zval_get_double(zv);
+}
+
+void redis_conf_bool(HashTable *ht, const char *key, int keylen, int *ival) {
+ zend_string *zstr = NULL;
+
+ redis_conf_string(ht, key, keylen, &zstr);
+ if (zstr == NULL)
+ return;
+
+ *ival = zend_string_equals_literal_ci(zstr, "true") ||
+ zend_string_equals_literal_ci(zstr, "yes") ||
+ zend_string_equals_literal_ci(zstr, "1");
+
+ zend_string_release(zstr);
+}
+
+void redis_conf_zend_bool(HashTable *ht, const char *key, int keylen, zend_bool *bval) {
+ zval *zv = zend_hash_str_find(ht, key, keylen);
+ if (zv == NULL)
+ return;
+
+ *bval = zend_is_true(zv);
+}
+
+void redis_conf_long(HashTable *ht, const char *key, int keylen, zend_long *lval) {
+ zval *zv = zend_hash_str_find(ht, key, keylen);
+ if (zv == NULL)
+ return;
+
+ *lval = zval_get_long(zv);
+}
+
+void redis_conf_int(HashTable *ht, const char *key, int keylen, int *ival) {
+ zval *zv = zend_hash_str_find(ht, key, keylen);
+ if (zv == NULL)
+ return;
+
+ *ival = zval_get_long(zv);
+}
+
+void redis_conf_string(HashTable *ht, const char *key, size_t keylen,
+ zend_string **sval)
+{
+ zval *zv = zend_hash_str_find(ht, key, keylen);
+ if (zv == NULL)
+ return;
+
+ *sval = zval_get_string(zv);
+}
+
+void redis_conf_zval(HashTable *ht, const char *key, size_t keylen, zval *zret,
+ int copy, int dtor)
+{
+ zval *zv = zend_hash_str_find(ht, key, keylen);
+ if (zv == NULL)
+ return;
+
+ ZVAL_ZVAL(zret, zv, copy, dtor);
+}
+
+void redis_conf_auth(HashTable *ht, const char *key, size_t keylen,
+ zend_string **user, zend_string **pass)
+{
+ zval *zv = zend_hash_str_find(ht, key, keylen);
+ if (zv == NULL)
+ return;
+
+ redis_extract_auth_info(zv, user, pass);
+}
+
/* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */
diff --git a/library.h b/library.h
index a5c660b6..da73b34d 100644
--- a/library.h
+++ b/library.h
@@ -20,19 +20,34 @@
#define CLUSTER_THROW_EXCEPTION(msg, code) \
zend_throw_exception(redis_cluster_exception_ce, (msg), code)
+#define redis_sock_write_sstr(redis_sock, sstr) \
+ redis_sock_write(redis_sock, (sstr)->c, (sstr)->len)
+
+#if PHP_VERSION_ID < 80000
+ #define redis_hash_fetch_ops(zstr) php_hash_fetch_ops(ZSTR_VAL((zstr)), ZSTR_LEN((zstr)))
+#else
+ #define redis_hash_fetch_ops(zstr) php_hash_fetch_ops(zstr)
+#endif
+
+void redis_register_persistent_resource(zend_string *id, void *ptr, int le_id);
+
+PHP_REDIS_API int redis_extract_auth_info(zval *ztest, zend_string **user, zend_string **pass);
+
int redis_cmd_init_sstr(smart_string *str, int num_args, char *keyword, int keyword_len);
int redis_cmd_append_sstr(smart_string *str, char *append, int append_len);
int redis_cmd_append_sstr_int(smart_string *str, int append);
int redis_cmd_append_sstr_long(smart_string *str, long append);
int redis_cmd_append_sstr_i64(smart_string *str, int64_t append);
int redis_cmd_append_sstr_dbl(smart_string *str, double value);
+int redis_cmd_append_sstr_zstr(smart_string *str, zend_string *zstr);
int redis_cmd_append_sstr_zval(smart_string *str, zval *z, RedisSock *redis_sock);
int redis_cmd_append_sstr_key(smart_string *str, char *key, size_t len, RedisSock *redis_sock, short *slot);
int redis_cmd_append_sstr_arrkey(smart_string *cmd, zend_string *kstr, zend_ulong idx);
PHP_REDIS_API int redis_spprintf(RedisSock *redis_sock, short *slot, char **ret, char *kw, char *fmt, ...);
+PHP_REDIS_API zend_string *redis_pool_spprintf(RedisSock *redis_sock, char *fmt, ...);
-PHP_REDIS_API char * redis_sock_read(RedisSock *redis_sock, int *buf_len);
+PHP_REDIS_API char *redis_sock_read(RedisSock *redis_sock, int *buf_len);
PHP_REDIS_API int redis_sock_gets(RedisSock *redis_sock, char *buf, int buf_size, size_t* line_len);
PHP_REDIS_API void redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API void redis_long_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval* z_tab, void *ctx);
@@ -52,13 +67,17 @@ PHP_REDIS_API RedisSock* redis_sock_create(char *host, int host_len, int port, d
PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock);
PHP_REDIS_API int redis_sock_server_open(RedisSock *redis_sock);
PHP_REDIS_API int redis_sock_auth(RedisSock *redis_sock);
+PHP_REDIS_API char *redis_sock_auth_cmd(RedisSock *redis_sock, int *cmdlen);
+PHP_REDIS_API void redis_sock_set_auth(RedisSock *redis_sock, zend_string *user, zend_string *pass);
+PHP_REDIS_API void redis_sock_set_auth_zval(RedisSock *redis_sock, zval *zv);
+PHP_REDIS_API void redis_sock_copy_auth(RedisSock *dst, RedisSock *src);
+PHP_REDIS_API void redis_sock_free_auth(RedisSock *redis_sock);
PHP_REDIS_API int redis_sock_disconnect(RedisSock *redis_sock, int force);
PHP_REDIS_API zval *redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab);
PHP_REDIS_API int redis_sock_read_single_line(RedisSock *redis_sock, char *buffer,
size_t buflen, size_t *linelen, int set_err);
PHP_REDIS_API char *redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes);
PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *_z_tab, void *ctx);
-//PHP_REDIS_API void redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int count, int unserialize);
PHP_REDIS_API void redis_mbulk_reply_loop(RedisSock *redis_sock, zval *z_tab, int count, int unserialize);
@@ -113,6 +132,12 @@ redis_read_xclaim_response(RedisSock *redis_sock, int count, zval *rv);
PHP_REDIS_API int
redis_read_xinfo_response(RedisSock *redis_sock, zval *z_ret, int elements);
+/* Specialized ACL reply handlers */
+PHP_REDIS_API int redis_read_acl_getuser_reply(RedisSock *redis_sock, zval *zret, long len);
+PHP_REDIS_API int redis_acl_getuser_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
+PHP_REDIS_API int redis_read_acl_log_reply(RedisSock *redis_sock, zval *zret, long count);
+PHP_REDIS_API int redis_acl_log_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
+
/*
* Variant Read methods, mostly to implement eval
*/
@@ -125,4 +150,38 @@ PHP_REDIS_API int redis_read_raw_variant_reply(INTERNAL_FUNCTION_PARAMETERS, Red
PHP_REDIS_API int redis_read_variant_reply_strings(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab);
+/* Helper methods to get configuration values from a HashTable. */
+
+#define REDIS_HASH_STR_FIND_STATIC(ht, sstr) \
+ zend_hash_str_find(ht, sstr, sizeof(sstr) - 1)
+#define REDIS_HASH_STR_FIND_TYPE_STATIC(ht, sstr, type) \
+ redis_hash_str_find_type(ht, sstr, sizeof(sstr) - 1, type)
+
+#define REDIS_CONF_DOUBLE_STATIC(ht, sstr, dval) \
+ redis_conf_double(ht, sstr, sizeof(sstr) - 1, dval)
+#define REDIS_CONF_BOOL_STATIC(ht, sstr, rval) \
+ redis_conf_bool(ht, sstr, sizeof(sstr) - 1, rval)
+#define REDIS_CONF_ZEND_BOOL_STATIC(ht, sstr, bval) \
+ redis_conf_zend_bool(ht, sstr, sizeof(sstr) - 1, bval)
+#define REDIS_CONF_LONG_STATIC(ht, sstr, lval) \
+ redis_conf_long(ht, sstr, sizeof(sstr) - 1, lval)
+#define REDIS_CONF_INT_STATIC(ht, sstr, ival) \
+ redis_conf_int(ht, sstr, sizeof(sstr) - 1, ival)
+#define REDIS_CONF_STRING_STATIC(ht, sstr, sval) \
+ redis_conf_string(ht, sstr, sizeof(sstr) - 1, sval)
+#define REDIS_CONF_ZVAL_STATIC(ht, sstr, zret, copy, dtor) \
+ redis_conf_zval(ht, sstr, sizeof(sstr) - 1, zret, copy, dtor)
+#define REDIS_CONF_AUTH_STATIC(ht, sstr, user, pass) \
+ redis_conf_auth(ht, sstr, sizeof(sstr) - 1, user, pass)
+
+zval *redis_hash_str_find_type(HashTable *ht, const char *key, int keylen, int type);
+void redis_conf_double(HashTable *ht, const char *key, int keylen, double *dval);
+void redis_conf_bool(HashTable *ht, const char *key, int keylen, int *bval);
+void redis_conf_zend_bool(HashTable *ht, const char *key, int keylen, zend_bool *bval);
+void redis_conf_long(HashTable *ht, const char *key, int keylen, zend_long *lval);
+void redis_conf_int(HashTable *ht, const char *key, int keylen, int *ival);
+void redis_conf_string(HashTable *ht, const char *key, size_t keylen, zend_string **sval);
+void redis_conf_zval(HashTable *ht, const char *key, size_t keylen, zval *zret, int copy, int dtor);
+void redis_conf_auth(HashTable *ht, const char *key, size_t keylen, zend_string **user, zend_string **pass);
+
#endif
diff --git a/php_redis.h b/php_redis.h
index af77ea00..a4ff9f4b 100644
--- a/php_redis.h
+++ b/php_redis.h
@@ -27,6 +27,7 @@
PHP_METHOD(Redis, __construct);
PHP_METHOD(Redis, __destruct);
+PHP_METHOD(Redis, acl);
PHP_METHOD(Redis, append);
PHP_METHOD(Redis, auth);
PHP_METHOD(Redis, bgSave);
@@ -252,6 +253,18 @@ PHP_METHOD(Redis, getPersistentID);
PHP_METHOD(Redis, getAuth);
PHP_METHOD(Redis, getMode);
+/* For convenience we store the salt as a printable hex string which requires 2
+ * characters per byte + 1 for the NULL terminator */
+#define REDIS_SALT_BYTES 32
+#define REDIS_SALT_SIZE ((2 * REDIS_SALT_BYTES) + 1)
+
+ZEND_BEGIN_MODULE_GLOBALS(redis)
+ char salt[REDIS_SALT_SIZE];
+ZEND_END_MODULE_GLOBALS(redis)
+
+ZEND_EXTERN_MODULE_GLOBALS(redis)
+#define REDIS_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(redis, v)
+
#ifdef ZTS
#include "TSRM.h"
#endif
@@ -261,8 +274,9 @@ PHP_MSHUTDOWN_FUNCTION(redis);
PHP_MINFO_FUNCTION(redis);
/* Redis response handler function callback prototype */
-typedef void (*ResultCallback)(INTERNAL_FUNCTION_PARAMETERS,
- RedisSock *redis_sock, zval *z_tab, void *ctx);
+typedef void (*ResultCallback)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
+
+typedef int (*FailableResultCallback)(INTERNAL_FUNCTION_PARAMETERS, RedisSock*, zval*, void*);
PHP_REDIS_API int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent);
diff --git a/redis.c b/redis.c
index 71e301ad..b361aab2 100644
--- a/redis.c
+++ b/redis.c
@@ -27,9 +27,11 @@
#include "redis_cluster.h"
#include "redis_commands.h"
#include "redis_sentinel.h"
+#include <standard/php_random.h>
#include <zend_exceptions.h>
#include <ext/standard/info.h>
+
#ifdef PHP_SESSION
#include <ext/session/php_session.h>
#endif
@@ -94,6 +96,7 @@ PHP_INI_BEGIN()
/* redis pconnect */
PHP_INI_ENTRY("redis.pconnect.pooling_enabled", "1", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.pconnect.connection_limit", "0", PHP_INI_ALL, NULL)
+ PHP_INI_ENTRY("redis.pconnect.pool_pattern", "", PHP_INI_ALL, NULL)
/* redis session */
PHP_INI_ENTRY("redis.session.locking_enabled", "0", PHP_INI_ALL, NULL)
@@ -183,7 +186,7 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_lrem, 0, 0, 3)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_auth, 0, 0, 1)
- ZEND_ARG_INFO(0, password)
+ ZEND_ARG_INFO(0, auth)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_select, 0, 0, 1)
@@ -200,6 +203,11 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_slaveof, 0, 0, 0)
ZEND_ARG_INFO(0, port)
ZEND_END_ARG_INFO()
+ZEND_BEGIN_ARG_INFO_EX(arginfo_acl, 0, 0, 1)
+ ZEND_ARG_INFO(0, subcmd)
+ ZEND_ARG_VARIADIC_INFO(0, args)
+ZEND_END_ARG_INFO()
+
/* }}} */
ZEND_BEGIN_ARG_INFO_EX(arginfo_migrate, 0, 0, 5)
@@ -247,6 +255,7 @@ static zend_function_entry redis_functions[] = {
PHP_ME(Redis, _prefix, arginfo_key, ZEND_ACC_PUBLIC)
PHP_ME(Redis, _serialize, arginfo_value, ZEND_ACC_PUBLIC)
PHP_ME(Redis, _unserialize, arginfo_value, ZEND_ACC_PUBLIC)
+ PHP_ME(Redis, acl, arginfo_acl, ZEND_ACC_PUBLIC)
PHP_ME(Redis, append, arginfo_key_value, ZEND_ACC_PUBLIC)
PHP_ME(Redis, auth, arginfo_auth, ZEND_ACC_PUBLIC)
PHP_ME(Redis, bgSave, arginfo_void, ZEND_ACC_PUBLIC)
@@ -497,6 +506,9 @@ static const zend_module_dep redis_deps[] = {
ZEND_MOD_END
};
+ZEND_DECLARE_MODULE_GLOBALS(redis)
+static PHP_GINIT_FUNCTION(redis);
+
zend_module_entry redis_module_entry = {
STANDARD_MODULE_HEADER_EX,
NULL,
@@ -509,7 +521,11 @@ zend_module_entry redis_module_entry = {
NULL,
PHP_MINFO(redis),
PHP_REDIS_VERSION,
- STANDARD_MODULE_PROPERTIES
+ PHP_MODULE_GLOBALS(redis),
+ PHP_GINIT(redis),
+ NULL,
+ NULL,
+ STANDARD_MODULE_PROPERTIES_EX
};
#ifdef COMPILE_DL_REDIS
@@ -756,6 +772,39 @@ static ZEND_RSRC_DTOR_FUNC(redis_connections_pool_dtor)
}
}
+static void redis_random_hex_bytes(char *dst, size_t dstsize) {
+ char chunk[9], *ptr = dst;
+ ssize_t rem = dstsize, len, clen;
+ size_t bytes;
+
+ /* We need two characters per hex byte */
+ bytes = dstsize / 2;
+ zend_string *s = zend_string_alloc(bytes, 0);
+
+ /* First try to have PHP generate the bytes */
+ if (php_random_bytes_silent(ZSTR_VAL(s), bytes) == SUCCESS) {
+ php_hash_bin2hex(dst, (unsigned char *)ZSTR_VAL(s), bytes);
+ zend_string_release(s);
+ return;
+ }
+
+ /* PHP shouldn't have failed, but generate manually if it did. */
+ while (rem > 0) {
+ clen = snprintf(chunk, sizeof(chunk), "%08x", rand());
+ len = rem >= clen ? clen : rem;
+ memcpy(ptr, chunk, len);
+ ptr += len; rem -= len;
+ }
+
+ zend_string_release(s);
+}
+
+static PHP_GINIT_FUNCTION(redis)
+{
+ redis_random_hex_bytes(redis_globals->salt, sizeof(redis_globals->salt) - 1);
+ redis_globals->salt[sizeof(redis_globals->salt)-1] = '\0';
+}
+
/**
* PHP_MINIT_FUNCTION
*/
@@ -973,7 +1022,7 @@ PHP_METHOD(Redis, pconnect)
PHP_REDIS_API int
redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent)
{
- zval *object, *ssl = NULL;
+ zval *object, *context = NULL, *ele;
char *host = NULL, *persistent_id = NULL;
zend_long port = -1, retry_interval = 0;
size_t host_len, persistent_id_len;
@@ -990,7 +1039,7 @@ redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent)
"Os|lds!lda", &object, redis_ce, &host,
&host_len, &port, &timeout, &persistent_id,
&persistent_id_len, &retry_interval,
- &read_timeout, &ssl) == FAILURE)
+ &read_timeout, &context) == FAILURE)
{
return FAILURE;
}
@@ -1021,6 +1070,7 @@ redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent)
}
redis = PHPREDIS_ZVAL_GET_OBJECT(redis_object, object);
+
/* if there is a redis sock already we have to remove it */
if (redis->sock) {
redis_sock_disconnect(redis->sock, 0);
@@ -1030,8 +1080,16 @@ redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent)
redis->sock = redis_sock_create(host, host_len, port, timeout, read_timeout, persistent,
persistent_id, retry_interval);
- if (ssl != NULL) {
- redis_sock_set_stream_context(redis->sock, ssl);
+ if (context) {
+ /* Stream context (e.g. TLS) */
+ if ((ele = REDIS_HASH_STR_FIND_STATIC(Z_ARRVAL_P(context), "stream"))) {
+ redis_sock_set_stream_context(redis->sock, ele);
+ }
+
+ /* AUTH */
+ if ((ele = REDIS_HASH_STR_FIND_STATIC(Z_ARRVAL_P(context), "auth"))) {
+ redis_sock_set_auth_zval(redis->sock, ele);
+ }
}
if (redis_sock_server_open(redis->sock) < 0) {
@@ -1335,6 +1393,53 @@ PHP_METHOD(Redis, type)
}
/* }}} */
+/* {{{ proto mixed Redis::acl(string $op, ...) }}} */
+PHP_METHOD(Redis, acl) {
+ RedisSock *redis_sock;
+ FailableResultCallback cb;
+ zval *zargs;
+ zend_string *op;
+ char *cmd;
+ int cmdlen, argc = ZEND_NUM_ARGS();
+
+ if (argc < 1 || (redis_sock = redis_sock_get(getThis(), 0)) == NULL) {
+ if (argc < 1) {
+ php_error_docref(NULL, E_WARNING, "ACL command requires at least one argument");
+ }
+ RETURN_FALSE;
+ }
+
+ zargs = emalloc(argc * sizeof(*zargs));
+ if (zend_get_parameters_array(ht, argc, zargs) == FAILURE) {
+ efree(zargs);
+ RETURN_FALSE;
+ }
+
+ /* Read the subcommand and set response callback */
+ op = zval_get_string(&zargs[0]);
+ if (zend_string_equals_literal_ci(op, "GETUSER")) {
+ cb = redis_acl_getuser_reply;
+ } else if (zend_string_equals_literal_ci(op, "LOG")) {
+ cb = redis_acl_log_reply;
+ } else {
+ cb = redis_read_variant_reply;
+ }
+
+ /* Make our command and free args */
+ cmd = redis_variadic_str_cmd("ACL", zargs, argc, &cmdlen);
+
+ zend_string_release(op);
+ efree(zargs);
+
+ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmdlen);
+ if (IS_ATOMIC(redis_sock)) {
+ if (cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) {
+ RETURN_FALSE;
+ }
+ }
+ REDIS_PROCESS_RESPONSE(cb);
+}
+
/* {{{ proto long Redis::append(string key, string val) */
PHP_METHOD(Redis, append)
{
@@ -2542,8 +2647,7 @@ redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAMETERS,
for (fi = redis_sock->head; fi; /* void */) {
if (fi->fun) {
- fi->fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab,
- fi->ctx);
+ fi->fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, fi->ctx);
fi = fi->next;
continue;
}
@@ -3309,13 +3413,25 @@ PHP_METHOD(Redis, getPersistentID) {
/* {{{ proto Redis::getAuth */
PHP_METHOD(Redis, getAuth) {
RedisSock *redis_sock;
+ zval zret;
- if ((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU)) == NULL) {
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "") == FAILURE)
+ RETURN_FALSE;
+
+ redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU);
+ if (redis_sock == NULL)
RETURN_FALSE;
- } else if (redis_sock->auth == NULL) {
+
+ if (redis_sock->user && redis_sock->pass) {
+ array_init(&zret);
+ add_next_index_str(&zret, zend_string_copy(redis_sock->user));
+ add_next_index_str(&zret, zend_string_copy(redis_sock->pass));
+ RETURN_ZVAL(&zret, 0, 0);
+ } else if (redis_sock->pass) {
+ RETURN_STR_COPY(redis_sock->pass);
+ } else {
RETURN_NULL();
}
- RETURN_STRINGL(ZSTR_VAL(redis_sock->auth), ZSTR_LEN(redis_sock->auth));
}
/*
diff --git a/redis_array.c b/redis_array.c
index bdd7120e..998d6a23 100644
--- a/redis_array.c
+++ b/redis_array.c
@@ -231,122 +231,67 @@ PHP_METHOD(RedisArray, __construct)
long l_retry_interval = 0;
zend_bool b_lazy_connect = 0;
double d_connect_timeout = 0, read_timeout = 0.0;
- zend_string *algorithm = NULL, *auth = NULL;
+ zend_string *algorithm = NULL, *user = NULL, *pass = NULL;
redis_array_object *obj;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|a", &z0, &z_opts) == FAILURE) {
RETURN_FALSE;
}
+ /* Bail if z0 isn't a string or an array.
+ * Note: WRONG_PARAM_COUNT seems wrong but this is what we have been doing
+ * for ages so we can't really change it until the next major version.
+ */
+ if (Z_TYPE_P(z0) != IS_ARRAY && Z_TYPE_P(z0) != IS_STRING)
+ WRONG_PARAM_COUNT;
+
+ /* If it's a string we want to load the array from ini information */
+ if (Z_TYPE_P(z0) == IS_STRING) {
+ ra = ra_load_array(Z_STRVAL_P(z0));
+ goto finish;
+ }
+
ZVAL_NULL(&z_fun);
ZVAL_NULL(&z_dist);
+
/* extract options */
if(z_opts) {
hOpts = Z_ARRVAL_P(z_opts);
/* extract previous ring. */
- if ((zpData = zend_hash_str_find(hOpts, "previous", sizeof("previous") - 1)) != NULL && Z_TYPE_P(zpData) == IS_ARRAY
- && zend_hash_num_elements(Z_ARRVAL_P(zpData)) != 0
- ) {
- /* consider previous array as non-existent if empty. */
+ zpData = REDIS_HASH_STR_FIND_STATIC(hOpts, "previous");
+ if (zpData && Z_TYPE_P(zpData) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(zpData)) > 0) {
hPrev = Z_ARRVAL_P(zpData);
}
- /* extract function name. */
- if ((zpData = zend_hash_str_find(hOpts, "function", sizeof("function") - 1)) != NULL) {
- ZVAL_ZVAL(&z_fun, zpData, 1, 0);
- }
-
- /* extract function name. */
- if ((zpData = zend_hash_str_find(hOpts, "distributor", sizeof("distributor") - 1)) != NULL) {
- ZVAL_ZVAL(&z_dist, zpData, 1, 0);
- }
-
- /* extract function name. */
- if ((zpData = zend_hash_str_find(hOpts, "algorithm", sizeof("algorithm") - 1)) != NULL && Z_TYPE_P(zpData) == IS_STRING) {
- algorithm = zval_get_string(zpData);
- }
- /* extract index option. */
- if ((zpData = zend_hash_str_find(hOpts, "index", sizeof("index") - 1)) != NULL) {
- b_index = zval_is_true(zpData);
- }
-
- /* extract autorehash option. */
- if ((zpData = zend_hash_str_find(hOpts, "autorehash", sizeof("autorehash") - 1)) != NULL) {
- b_autorehash = zval_is_true(zpData);
- }
-
- /* pconnect */
- if ((zpData = zend_hash_str_find(hOpts, "pconnect", sizeof("pconnect") - 1)) != NULL) {
- b_pconnect = zval_is_true(zpData);
- }
-
- /* extract retry_interval option. */
- if ((zpData = zend_hash_str_find(hOpts, "retry_interval", sizeof("retry_interval") - 1)) != NULL) {
- if (Z_TYPE_P(zpData) == IS_LONG) {
- l_retry_interval = Z_LVAL_P(zpData);
- } else if (Z_TYPE_P(zpData) == IS_STRING) {
- l_retry_interval = atol(Z_STRVAL_P(zpData));
- }
- }
-
- /* extract lazy connect option. */
- if ((zpData = zend_hash_str_find(hOpts, "lazy_connect", sizeof("lazy_connect") - 1)) != NULL) {
- b_lazy_connect = zval_is_true(zpData);
- }
-
- /* extract connect_timeout option */
- if ((zpData = zend_hash_str_find(hOpts, "connect_timeout", sizeof("connect_timeout") - 1)) != NULL) {
- if (Z_TYPE_P(zpData) == IS_DOUBLE) {
- d_connect_timeout = Z_DVAL_P(zpData);
- } else if (Z_TYPE_P(zpData) == IS_LONG) {
- d_connect_timeout = Z_LVAL_P(zpData);
- } else if (Z_TYPE_P(zpData) == IS_STRING) {
- d_connect_timeout = atof(Z_STRVAL_P(zpData));
- }
- }
-
- /* extract read_timeout option */
- if ((zpData = zend_hash_str_find(hOpts, "read_timeout", sizeof("read_timeout") - 1)) != NULL) {
- if (Z_TYPE_P(zpData) == IS_DOUBLE) {
- read_timeout = Z_DVAL_P(zpData);
- } else if (Z_TYPE_P(zpData) == IS_LONG) {
- read_timeout = Z_LVAL_P(zpData);
- } else if (Z_TYPE_P(zpData) == IS_STRING) {
- read_timeout = atof(Z_STRVAL_P(zpData));
- }
- }
-
- /* consistent */
- if ((zpData = zend_hash_str_find(hOpts, "consistent", sizeof("consistent") - 1)) != NULL) {
- consistent = zval_is_true(zpData);
- }
-
- /* auth */
- if ((zpData = zend_hash_str_find(hOpts, "auth", sizeof("auth") - 1)) != NULL) {
- auth = zval_get_string(zpData);
- }
+ REDIS_CONF_AUTH_STATIC(hOpts, "auth", &user, &pass);
+ REDIS_CONF_ZVAL_STATIC(hOpts, "function", &z_fun, 1, 0);
+ REDIS_CONF_ZVAL_STATIC(hOpts, "distributor", &z_dist, 1, 0);
+ REDIS_CONF_STRING_STATIC(hOpts, "algorithm", &algorithm);
+ REDIS_CONF_ZEND_BOOL_STATIC(hOpts, "index", &b_index);
+ REDIS_CONF_ZEND_BOOL_STATIC(hOpts, "autorehash", &b_autorehash);
+ REDIS_CONF_ZEND_BOOL_STATIC(hOpts, "pconnect", &b_pconnect);
+ REDIS_CONF_LONG_STATIC(hOpts, "retry_interval", &l_retry_interval);
+ REDIS_CONF_ZEND_BOOL_STATIC(hOpts, "lazy_connect", &b_lazy_connect);
+ REDIS_CONF_ZEND_BOOL_STATIC(hOpts, "consistent", &consistent);
+ REDIS_CONF_DOUBLE_STATIC(hOpts, "connect_timeout", &d_connect_timeout);
+ REDIS_CONF_DOUBLE_STATIC(hOpts, "read_timeout", &read_timeout);
}
- /* extract either name of list of hosts from z0 */
- switch(Z_TYPE_P(z0)) {
- case IS_STRING:
- ra = ra_load_array(Z_STRVAL_P(z0));
- break;
+ ra = ra_make_array(Z_ARRVAL_P(z0), &z_fun, &z_dist, hPrev, b_index,
+ b_pconnect, l_retry_interval, b_lazy_connect,
+ d_connect_timeout, read_timeout, consistent,
+ algorithm, user, pass);
- case IS_ARRAY:
- ra = ra_make_array(Z_ARRVAL_P(z0), &z_fun, &z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout, read_timeout, consistent, algorithm, auth);
- break;
-
- default:
- WRONG_PARAM_COUNT;
- }
if (algorithm) zend_string_release(algorithm);
- if (auth) zend_string_release(auth);
+ if (user) zend_string_release(user);
+ if (pass) zend_string_release(pass);
zval_dtor(&z_dist);
zval_dtor(&z_fun);
+finish:
+
if(ra) {
ra->auto_rehash = b_autorehash;
ra->connect_timeout = d_connect_timeout;
@@ -357,7 +302,9 @@ PHP_METHOD(RedisArray, __construct)
}
static void
-ra_forward_call(INTERNAL_FUNCTION_PARAMETERS, RedisArray *ra, const char *cmd, int cmd_len, zval *z_args, zval *z_new_target) {
+ra_forward_call(INTERNAL_FUNCTION_PARAMETERS, RedisArray *ra, const char *cmd,
+ int cmd_len, zval *z_args, zval *z_new_target)
+{
zval z_fun, *redis_inst, *z_callargs, *zp_tmp;
char *key = NULL; /* set to avoid "unused-but-set-variable" */
diff --git a/redis_array_impl.c b/redis_array_impl.c
index 82715d07..a427f673 100644
--- a/redis_array_impl.c
+++ b/redis_array_impl.c
@@ -31,7 +31,8 @@
extern zend_class_entry *redis_ce;
static RedisArray *
-ra_load_hosts(RedisArray *ra, HashTable *hosts, zend_string *auth, long retry_interval, zend_bool b_lazy_connect)
+ra_load_hosts(RedisArray *ra, HashTable *hosts, zend_string *user,
+ zend_string *pass, long retry_interval, zend_bool b_lazy_connect)
{
int i = 0, host_len;
char *host, *p;
@@ -63,14 +64,13 @@ ra_load_hosts(RedisArray *ra, HashTable *hosts, zend_string *auth, long retry_in
redis = PHPREDIS_ZVAL_GET_OBJECT(redis_object, &ra->redis[i]);
/* create socket */
- redis->sock = redis_sock_create(host, host_len, port, ra->connect_timeout, ra->read_timeout, ra->pconnect, NULL, retry_interval);
+ redis->sock = redis_sock_create(host, host_len, port, ra->connect_timeout,
+ ra->read_timeout, ra->pconnect, NULL,
+ retry_interval);
- /* copy password is specified */
- if (auth) redis->sock->auth = zend_string_copy(auth);
+ redis_sock_set_auth(redis->sock, user, pass);
- if (!b_lazy_connect)
- {
- /* connect */
+ if (!b_lazy_connect) {
if (redis_sock_server_open(redis->sock) < 0) {
ra->count = ++i;
return NULL;
@@ -151,25 +151,12 @@ ra_find_name(const char *name) {
/* load array from INI settings */
RedisArray *ra_load_array(const char *name) {
-
- zval *z_data, z_fun, z_dist;
+ zval *z_data, z_tmp, z_fun, z_dist;
zval z_params_hosts;
zval z_params_prev;
- zval z_params_funs;
- zval z_params_dist;
- zval z_params_algo;
- zval z_params_index;
- zval z_params_autorehash;
- zval z_params_retry_interval;
- zval z_params_pconnect;
- zval z_params_connect_timeout;
- zval z_params_read_timeout;
- zval z_params_lazy_connect;
- zval z_params_consistent;
- zval z_params_auth;
RedisArray *ra = NULL;
- zend_string *algorithm = NULL, *auth = NULL;
+ zend_string *algorithm = NULL, *user = NULL, *pass = NULL;
zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0, consistent = 0;
long l_retry_interval = 0;
zend_bool b_lazy_connect = 0;
@@ -182,185 +169,142 @@ RedisArray *ra_load_array(const char *name) {
if(!ra_find_name(name))
return ra;
+ ZVAL_NULL(&z_fun);
+ ZVAL_NULL(&z_dist);
+
/* find hosts */
array_init(&z_params_hosts);
if ((iptr = INI_STR("redis.arrays.hosts")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_hosts);
- }
- if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_hosts), name, name_len)) != NULL) {
- hHosts = Z_ARRVAL_P(z_data);
+ if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_hosts), name, name_len)) != NULL) {
+ hHosts = Z_ARRVAL_P(z_data);
+ }
}
/* find previous hosts */
array_init(&z_params_prev);
if ((iptr = INI_STR("redis.arrays.previous")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_prev);
- }
- if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_prev), name, name_len)) != NULL) {
- hPrev = Z_ARRVAL_P(z_data);
+ if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_prev), name, name_len)) != NULL) {
+ if (Z_TYPE_P(z_data) == IS_ARRAY) {
+ hPrev = Z_ARRVAL_P(z_data);
+ }
+ }
}
/* find function */
- array_init(&z_params_funs);
if ((iptr = INI_STR("redis.arrays.functions")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_funs);
- }
- ZVAL_NULL(&z_fun);
- if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_funs), name, name_len)) != NULL) {
- ZVAL_ZVAL(&z_fun, z_data, 1, 0);
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ redis_conf_zval(Z_ARRVAL(z_tmp), name, name_len, &z_fun, 1, 0);
+ zval_dtor(&z_tmp);
}
/* find distributor */
- array_init(&z_params_dist);
if ((iptr = INI_STR("redis.arrays.distributor")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_dist);
- }
- ZVAL_NULL(&z_dist);
- if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_dist), name, name_len)) != NULL) {
- ZVAL_ZVAL(&z_dist, z_data, 1, 0);
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ redis_conf_zval(Z_ARRVAL(z_tmp), name, name_len, &z_dist, 1, 0);
+ zval_dtor(&z_tmp);
}
/* find hash algorithm */
- array_init(&z_params_algo);
if ((iptr = INI_STR("redis.arrays.algorithm")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_algo);
- }
- if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_algo), name, name_len)) != NULL) {
- algorithm = zval_get_string(z_data);
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ redis_conf_string(Z_ARRVAL(z_tmp), name, name_len, &algorithm);
+ zval_dtor(&z_tmp);
}
/* find index option */
- array_init(&z_params_index);
if ((iptr = INI_STR("redis.arrays.index")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_index);
- }
- if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_index), name, name_len)) != NULL) {
- if (Z_TYPE_P(z_data) == IS_STRING && strncmp(Z_STRVAL_P(z_data), "1", 1) == 0) {
- b_index = 1;
- }
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ redis_conf_zend_bool(Z_ARRVAL(z_tmp), name, name_len, &b_index);
+ zval_dtor(&z_tmp);
}
/* find autorehash option */
- array_init(&z_params_autorehash);
if ((iptr = INI_STR("redis.arrays.autorehash")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_autorehash);
- }
- if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_autorehash), name, name_len)) != NULL) {
- if(Z_TYPE_P(z_data) == IS_STRING && strncmp(Z_STRVAL_P(z_data), "1", 1) == 0) {
- b_autorehash = 1;
- }
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ redis_conf_zend_bool(Z_ARRVAL(z_tmp), name, name_len, &b_autorehash);
+ zval_dtor(&z_tmp);
}
/* find retry interval option */
- array_init(&z_params_retry_interval);
if ((iptr = INI_STR("redis.arrays.retryinterval")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_retry_interval);
- }
- if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_retry_interval), name, name_len)) != NULL) {
- if (Z_TYPE_P(z_data) == IS_LONG) {
- l_retry_interval = Z_LVAL_P(z_data);
- } else if (Z_TYPE_P(z_data) == IS_STRING) {
- l_retry_interval = atol(Z_STRVAL_P(z_data));
- }
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ redis_conf_long(Z_ARRVAL(z_tmp), name, name_len, &l_retry_interval);
+ zval_dtor(&z_tmp);
}
/* find pconnect option */
- array_init(&z_params_pconnect);
if ((iptr = INI_STR("redis.arrays.pconnect")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_pconnect);
- }
- if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_pconnect), name, name_len)) != NULL) {
- if(Z_TYPE_P(z_data) == IS_STRING && strncmp(Z_STRVAL_P(z_data), "1", 1) == 0) {
- b_pconnect = 1;
- }
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ redis_conf_zend_bool(Z_ARRVAL(z_tmp), name, name_len, &b_pconnect);
+ zval_dtor(&z_tmp);
}
/* find lazy connect option */
- array_init(&z_params_lazy_connect);
if ((iptr = INI_STR("redis.arrays.lazyconnect")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_lazy_connect);
- }
- if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_lazy_connect), name, name_len)) != NULL) {
- if (Z_TYPE_P(z_data) == IS_STRING && strncmp(Z_STRVAL_P(z_data), "1", 1) == 0) {
- b_lazy_connect = 1;
- }
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ redis_conf_zend_bool(Z_ARRVAL(z_tmp), name, name_len, &b_lazy_connect);
+ zval_dtor(&z_tmp);
}
/* find connect timeout option */
- array_init(&z_params_connect_timeout);
if ((iptr = INI_STR("redis.arrays.connecttimeout")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_connect_timeout);
- }
- if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_connect_timeout), name, name_len)) != NULL) {
- if (Z_TYPE_P(z_data) == IS_DOUBLE) {
- d_connect_timeout = Z_DVAL_P(z_data);
- } else if (Z_TYPE_P(z_data) == IS_STRING) {
- d_connect_timeout = atof(Z_STRVAL_P(z_data));
- } else if (Z_TYPE_P(z_data) == IS_LONG) {
- d_connect_timeout = Z_LVAL_P(z_data);
- }
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ redis_conf_double(Z_ARRVAL(z_tmp), name, name_len, &d_connect_timeout);
+ zval_dtor(&z_tmp);
}
/* find read timeout option */
- array_init(&z_params_read_timeout);
if ((iptr = INI_STR("redis.arrays.readtimeout")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_read_timeout);
- }
- if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_read_timeout), name, name_len)) != NULL) {
- if (Z_TYPE_P(z_data) == IS_DOUBLE) {
- read_timeout = Z_DVAL_P(z_data);
- } else if (Z_TYPE_P(z_data) == IS_STRING) {
- read_timeout = atof(Z_STRVAL_P(z_data));
- } else if (Z_TYPE_P(z_data) == IS_LONG) {
- read_timeout = Z_LVAL_P(z_data);
- }
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ redis_conf_double(Z_ARRVAL(z_tmp), name, name_len, &read_timeout);
+ zval_dtor(&z_tmp);
}
/* find consistent option */
- array_init(&z_params_consistent);
if ((iptr = INI_STR("redis.arrays.consistent")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_consistent);
- }
- if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_consistent), name, name_len)) != NULL) {
- if (Z_TYPE_P(z_data) == IS_STRING && strncmp(Z_STRVAL_P(z_data), "1", 1) == 0) {
- consistent = 1;
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ if ((z_data = zend_hash_str_find(Z_ARRVAL(z_tmp), name, name_len)) != NULL) {
+ consistent = Z_TYPE_P(z_data) == IS_STRING && strncmp(Z_STRVAL_P(z_data), "1", 1) == 0;
}
+ zval_dtor(&z_tmp);
}
/* find auth option */
- array_init(&z_params_auth);
if ((iptr = INI_STR("redis.arrays.auth")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_auth);
- }
- if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_auth), name, name_len)) != NULL) {
- auth = zval_get_string(z_data);
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ redis_conf_auth(Z_ARRVAL(z_tmp), name, name_len, &user, &pass);
+ zval_dtor(&z_tmp);
}
/* create RedisArray object */
- ra = ra_make_array(hHosts, &z_fun, &z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout, read_timeout, consistent, algorithm, auth);
+ ra = ra_make_array(hHosts, &z_fun, &z_dist, hPrev, b_index, b_pconnect, l_retry_interval,
+ b_lazy_connect, d_connect_timeout, read_timeout, consistent, algorithm,
+ user, pass);
if (ra) {
ra->auto_rehash = b_autorehash;
if(ra->prev) ra->prev->auto_rehash = b_autorehash;
}
- /* cleanup */
if (algorithm) zend_string_release(algorithm);
- if (auth) zend_string_release(auth);
+ if (user) zend_string_release(user);
+ if (pass) zend_string_release(pass);
zval_dtor(&z_params_hosts);
zval_dtor(&z_params_prev);
- zval_dtor(&z_params_funs);
- zval_dtor(&z_params_dist);
- zval_dtor(&z_params_algo);
- zval_dtor(&z_params_index);
- zval_dtor(&z_params_autorehash);
- zval_dtor(&z_params_retry_interval);
- zval_dtor(&z_params_pconnect);
- zval_dtor(&z_params_connect_timeout);
- zval_dtor(&z_params_read_timeout);
- zval_dtor(&z_params_lazy_connect);
- zval_dtor(&z_params_consistent);
- zval_dtor(&z_params_auth);
zval_dtor(&z_dist);
zval_dtor(&z_fun);
@@ -408,7 +352,11 @@ ra_make_continuum(zend_string **hosts, int nb_hosts)
}
RedisArray *
-ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout, double read_timeout, zend_bool consistent, zend_string *algorithm, zend_string *auth)
+ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev,
+ zend_bool b_index, zend_bool b_pconnect, long retry_interval,
+ zend_bool b_lazy_connect, double connect_timeout, double read_timeout,
+ zend_bool consistent, zend_string *algorithm, zend_string *user,
+ zend_string *pass)
{
int i, count;
RedisArray *ra;
@@ -429,7 +377,7 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev
ra->continuum = NULL;
ra->algorithm = NULL;
- if (ra_load_hosts(ra, hosts, auth, retry_interval, b_lazy_connect) == NULL || !ra->count) {
+ if (ra_load_hosts(ra, hosts, user, pass, retry_interval, b_lazy_connect) == NULL || !ra->count) {
for (i = 0; i < ra->count; ++i) {
zval_dtor(&ra->redis[i]);
zend_string_release(ra->hosts[i]);
@@ -439,7 +387,8 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev
efree(ra);
return NULL;
}
- ra->prev = hosts_prev ? ra_make_array(hosts_prev, z_fun, z_dist, NULL, b_index, b_pconnect, retry_interval, b_lazy_connect, connect_timeout, read_timeout, consistent, algorithm, auth) : NULL;
+
+ ra->prev = hosts_prev ? ra_make_array(hosts_prev, z_fun, z_dist, NULL, b_index, b_pconnect, retry_interval, b_lazy_connect, connect_timeout, read_timeout, consistent, algorithm, user, pass) : NULL;
/* init array data structures */
ra_init_function_table(ra);
@@ -541,13 +490,7 @@ ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos)
const php_hash_ops *ops;
/* hash */
- if (ra->algorithm && (
-#if (PHP_VERSION_ID < 80000)
- ops = php_hash_fetch_ops(ZSTR_VAL(ra->algorithm), ZSTR_LEN(ra->algorithm))
-#else
- ops = php_hash_fetch_ops(ra->algorithm)
-#endif
- ) != NULL) {
+ if (ra->algorithm && (ops = redis_hash_fetch_ops(ra->algorithm))) {
void *ctx = emalloc(ops->context_size);
unsigned char *digest = emalloc(ops->digest_size);
diff --git a/redis_array_impl.h b/redis_array_impl.h
index b5d2e1ce..0ef5a73f 100644
--- a/redis_array_impl.h
+++ b/redis_array_impl.h
@@ -10,7 +10,15 @@
#include "redis_array.h"
RedisArray *ra_load_array(const char *name);
-RedisArray *ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout, double read_timeout, zend_bool consistent, zend_string *algorithm, zend_string *auth);
+
+RedisArray *ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist,
+ HashTable *hosts_prev, zend_bool b_index,
+ zend_bool b_pconnect, long retry_interval,
+ zend_bool b_lazy_connect, double connect_timeout,
+ double read_timeout, zend_bool consistent,
+ zend_string *algorithm, zend_string *auth,
+ zend_string *pass);
+
zval *ra_find_node_by_name(RedisArray *ra, const char *host, int host_len);
zval *ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos);
void ra_init_function_table(RedisArray *ra);
diff --git a/redis_cluster.c b/redis_cluster.c
index a2ced22a..3233b653 100644
--- a/redis_cluster.c
+++ b/redis_cluster.c
@@ -96,6 +96,12 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_scan_cl, 0, 0, 2)
ZEND_ARG_INFO(0, i_count)
ZEND_END_ARG_INFO()
+ZEND_BEGIN_ARG_INFO_EX(arginfo_acl_cl, 0, 0, 2)
+ ZEND_ARG_INFO(0, key_or_address)
+ ZEND_ARG_INFO(0, subcmd)
+ ZEND_ARG_VARIADIC_INFO(0, args)
+ZEND_END_ARG_INFO()
+
/* Function table */
zend_function_entry redis_cluster_functions[] = {
PHP_ME(RedisCluster, __construct, arginfo_ctor, ZEND_ACC_PUBLIC)
@@ -104,6 +110,7 @@ zend_function_entry redis_cluster_functions[] = {
PHP_ME(RedisCluster, _redir, arginfo_void, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, _serialize, arginfo_value, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, _unserialize, arginfo_value, ZEND_ACC_PUBLIC)
+ PHP_ME(RedisCluster, acl, arginfo_acl_cl, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, append, arginfo_key_value, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, bgrewriteaof, arginfo_key_or_address, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, bgsave, arginfo_key_or_address, ZEND_ACC_PUBLIC)
@@ -343,8 +350,8 @@ void free_cluster_context(zend_object *object) {
/* Take user provided seeds and return unique and valid ones */
/* Attempt to connect to a Redis cluster provided seeds and timeout options */
static void redis_cluster_init(redisCluster *c, HashTable *ht_seeds, double timeout,
- double read_timeout, int persistent, char *auth,
- size_t auth_len)
+ double read_timeout, int persistent, zend_string *user,
+ zend_string *pass)
{
zend_string *hash = NULL, **seeds;
redisCachedCluster *cc;
@@ -358,9 +365,10 @@ static void redis_cluster_init(redisCluster *c, HashTable *ht_seeds, double time
return;
}
- if (auth && auth_len) {
- c->flags->auth = zend_string_init(auth, auth_len, 0);
- }
+ if (user && ZSTR_LEN(user))
+ c->flags->user = zend_string_copy(user);
+ if (pass && ZSTR_LEN(pass))
+ c->flags->pass = zend_string_copy(pass);
c->timeout = timeout;
c->read_timeout = read_timeout;
@@ -390,11 +398,11 @@ cleanup:
/* Attempt to load a named cluster configured in php.ini */
void redis_cluster_load(redisCluster *c, char *name, int name_len) {
- zval z_seeds, z_timeout, z_read_timeout, z_persistent, z_auth, *z_value;
- char *iptr, *auth = NULL;
- size_t auth_len = 0;
+ zval z_seeds, z_tmp, *z_value;
+ zend_string *user = NULL, *pass = NULL;
double timeout = 0, read_timeout = 0;
int persistent = 0;
+ char *iptr;
HashTable *ht_seeds = NULL;
/* Seeds */
@@ -411,69 +419,43 @@ void redis_cluster_load(redisCluster *c, char *name, int name_len) {
}
/* Connection timeout */
- array_init(&z_timeout);
if ((iptr = INI_STR("redis.clusters.timeout")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_timeout);
- }
- if ((z_value = zend_hash_str_find(Z_ARRVAL(z_timeout), name, name_len)) != NULL) {
- if (Z_TYPE_P(z_value) == IS_STRING) {
- timeout = atof(Z_STRVAL_P(z_value));
- } else if (Z_TYPE_P(z_value) == IS_DOUBLE) {
- timeout = Z_DVAL_P(z_value);
- } else if (Z_TYPE_P(z_value) == IS_LONG) {
- timeout = Z_LVAL_P(z_value);
- }
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ redis_conf_double(Z_ARRVAL(z_tmp), name, name_len, &timeout);
+ zval_dtor(&z_tmp);
}
/* Read timeout */
- array_init(&z_read_timeout);
if ((iptr = INI_STR("redis.clusters.read_timeout")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_read_timeout);
- }
- if ((z_value = zend_hash_str_find(Z_ARRVAL(z_read_timeout), name, name_len)) != NULL) {
- if (Z_TYPE_P(z_value) == IS_STRING) {
- read_timeout = atof(Z_STRVAL_P(z_value));
- } else if (Z_TYPE_P(z_value) == IS_DOUBLE) {
- read_timeout = Z_DVAL_P(z_value);
- } else if (Z_TYPE_P(z_value) == IS_LONG) {
- read_timeout = Z_LVAL_P(z_value);
- }
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ redis_conf_double(Z_ARRVAL(z_tmp), name, name_len, &read_timeout);
+ zval_dtor(&z_tmp);
}
/* Persistent connections */
- array_init(&z_persistent);
if ((iptr = INI_STR("redis.clusters.persistent")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_persistent);
- }
- if ((z_value = zend_hash_str_find(Z_ARRVAL(z_persistent), name, name_len)) != NULL) {
- if (Z_TYPE_P(z_value) == IS_STRING) {
- persistent = atoi(Z_STRVAL_P(z_value));
- } else if (Z_TYPE_P(z_value) == IS_LONG) {
- persistent = Z_LVAL_P(z_value);
- }
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ redis_conf_bool(Z_ARRVAL(z_tmp), name, name_len, &persistent);
+ zval_dtor(&z_tmp);
}
- /* Cluster auth */
- array_init(&z_auth);
- if ((iptr = INI_STR("redis.clusters.auth")) != NULL) {
- sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_auth);
- }
- if ((z_value = zend_hash_str_find(Z_ARRVAL(z_auth), name, name_len)) != NULL &&
- Z_TYPE_P(z_value) == IS_STRING && Z_STRLEN_P(z_value) > 0
- ) {
- auth = Z_STRVAL_P(z_value);
- auth_len = Z_STRLEN_P(z_value);
+ if ((iptr = INI_STR("redis.clusters.auth"))) {
+ array_init(&z_tmp);
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
+ redis_conf_auth(Z_ARRVAL(z_tmp), name, name_len, &user, &pass);
+ zval_dtor(&z_tmp);
}
/* Attempt to create/connect to the cluster */
- redis_cluster_init(c, ht_seeds, timeout, read_timeout, persistent, auth, auth_len);
+ redis_cluster_init(c, ht_seeds, timeout, read_timeout, persistent, user, pass);
- /* Clean up our arrays */
+ /* Clean up */
zval_dtor(&z_seeds);
- zval_dtor(&z_timeout);
- zval_dtor(&z_read_timeout);
- zval_dtor(&z_persistent);
- zval_dtor(&z_auth);
+ if (user) zend_string_release(user);
+ if (pass) zend_string_release(pass);
}
/*
@@ -482,18 +464,19 @@ void redis_cluster_load(redisCluster *c, char *name, int name_len) {
/* Create a RedisCluster Object */
PHP_METHOD(RedisCluster, __construct) {
- zval *object, *z_seeds = NULL;
- char *name, *auth = NULL;
- size_t name_len, auth_len = 0;
+ zval *object, *z_seeds = NULL, *z_auth = NULL;
+ zend_string *user = NULL, *pass = NULL;
double timeout = 0.0, read_timeout = 0.0;
+ size_t name_len;
zend_bool persistent = 0;
redisCluster *context = GET_CONTEXT();
+ char *name;
// Parse arguments
if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
- "Os!|addbs", &object, redis_cluster_ce, &name,
+ "Os!|addbz", &object, redis_cluster_ce, &name,
&name_len, &z_seeds, &timeout, &read_timeout,
- &persistent, &auth, &auth_len) == FAILURE)
+ &persistent, &z_auth) == FAILURE)
{
RETURN_FALSE;
}
@@ -503,14 +486,19 @@ PHP_METHOD(RedisCluster, __construct) {
CLUSTER_THROW_EXCEPTION("You must specify a name or pass seeds!", 0);
}
- /* If we've been passed only one argument, the user is attempting to connect
- * to a named cluster, stored in php.ini, otherwise we'll need manual seeds */
- if (ZEND_NUM_ARGS() > 1) {
- redis_cluster_init(context, Z_ARRVAL_P(z_seeds), timeout, read_timeout,
- persistent, auth, auth_len);
- } else {
+ /* If we've got a string try to load from INI */
+ if (ZEND_NUM_ARGS() < 2) {
redis_cluster_load(context, name, name_len);
+ return;
}
+
+ /* The normal case, loading from arguments */
+ redis_extract_auth_info(z_auth, &user, &pass);
+ redis_cluster_init(context, Z_ARRVAL_P(z_seeds), timeout, read_timeout,
+ persistent, user, pass);
+
+ if (user) zend_string_release(user);
+ if (pass) zend_string_release(pass);
}
/*
@@ -2498,6 +2486,90 @@ static void cluster_kscan_cmd(INTERNAL_FUNCTION_PARAMETERS,
Z_LVAL_P(z_it) = it;
}
+static int redis_acl_op_readonly(zend_string *op) {
+ /* Only return read-only for operations we know to be */
+ if (ZSTR_STRICMP_STATIC(op, "LIST") ||
+ ZSTR_STRICMP_STATIC(op, "USERS") ||
+ ZSTR_STRICMP_STATIC(op, "GETUSER") ||
+ ZSTR_STRICMP_STATIC(op, "CAT") ||
+ ZSTR_STRICMP_STATIC(op, "GENPASS") ||
+ ZSTR_STRICMP_STATIC(op, "WHOAMI") ||
+ ZSTR_STRICMP_STATIC(op, "LOG")) return 1;
+
+ return 0;
+}
+
+PHP_METHOD(RedisCluster, acl) {
+ redisCluster *c = GET_CONTEXT();
+ smart_string cmdstr = {0};
+ int argc = ZEND_NUM_ARGS(), i, readonly;
+ cluster_cb cb;
+ zend_string *zs;
+ zval *zargs;
+ void *ctx = NULL;
+ short slot;
+
+ /* ACL in cluster needs a slot argument, and then at least the op */
+ if (argc < 2) {
+ WRONG_PARAM_COUNT;
+ RETURN_FALSE;
+ }
+
+ /* Grab all our arguments and determine the command slot */
+ zargs = emalloc(argc * sizeof(*zargs));
+ if (zend_get_parameters_array(ht, argc, zargs) == FAILURE ||
+ (slot = cluster_cmd_get_slot(c, &zargs[0]) < 0))
+ {
+ efree(zargs);
+ RETURN_FALSE;
+ }
+
+ REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc - 1, "ACL");
+
+ /* Read the op, determin if it's readonly, and add it */
+ zs = zval_get_string(&zargs[1]);
+ readonly = redis_acl_op_readonly(zs);
+ redis_cmd_append_sstr_zstr(&cmdstr, zs);
+
+ /* We have specialized handlers for GETUSER and LOG, whereas every
+ * other ACL command can be handled generically */
+ if (zend_string_equals_literal_ci(zs, "GETUSER")) {
+ cb = cluster_acl_getuser_resp;
+ } else if (zend_string_equals_literal_ci(zs, "LOG")) {
+ cb = cluster_acl_log_resp;
+ } else {
+ cb = cluster_variant_resp;
+ }
+
+ zend_string_release(zs);
+
+ /* Process remaining args */
+ for (i = 2; i < argc; i++) {
+ zs = zval_get_string(&zargs[i]);
+ redis_cmd_append_sstr_zstr(&cmdstr, zs);
+ zend_string_release(zs);
+ }
+
+ /* Can we use replicas? */
+ c->readonly = readonly && CLUSTER_IS_ATOMIC(c);
+
+ /* Kick off our command */
+ if (cluster_send_slot(c, slot, cmdstr.c, cmdstr.len, TYPE_EOF) < 0) {
+ CLUSTER_THROW_EXCEPTION("Unabler to send ACL command", 0);
+ efree(zargs);
+ RETURN_FALSE;
+ }
+
+ if (CLUSTER_IS_ATOMIC(c)) {
+ cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
+ } else {
+ CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx);
+ }
+
+ efree(cmdstr.c);
+ efree(zargs);
+}
+
/* {{{ proto RedisCluster::scan(string master, long it [, string pat, long cnt]) */
PHP_METHOD(RedisCluster, scan) {
redisCluster *c = GET_CONTEXT();
diff --git a/redis_cluster.h b/redis_cluster.h
index 379d034c..c6959fde 100644
--- a/redis_cluster.h
+++ b/redis_cluster.h
@@ -99,6 +99,7 @@ void free_cluster_context(zend_object *object);
/* RedisCluster method implementation */
PHP_METHOD(RedisCluster, __construct);
+PHP_METHOD(RedisCluster, acl);
PHP_METHOD(RedisCluster, close);
PHP_METHOD(RedisCluster, get);
PHP_METHOD(RedisCluster, set);
diff --git a/redis_commands.c b/redis_commands.c
index 97442de3..3b191944 100644
--- a/redis_commands.c
+++ b/redis_commands.c
@@ -2047,26 +2047,47 @@ int redis_pfcount_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
return SUCCESS;
}
+char *redis_variadic_str_cmd(char *kw, zval *argv, int argc, int *cmd_len) {
+ smart_string cmdstr = {0};
+ zend_string *zstr;
+ int i;
+
+ redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
+
+ for (i = 0; i < argc; i++) {
+ zstr = zval_get_string(&argv[i]);
+ redis_cmd_append_sstr_zstr(&cmdstr, zstr);
+ zend_string_release(zstr);
+ }
+
+ *cmd_len = cmdstr.len;
+ return cmdstr.c;
+}
+
int redis_auth_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
- char *pw;
- size_t pw_len;
+ zend_string *user = NULL, *pass = NULL;
+ zval *ztest;
- if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &pw, &pw_len)
- ==FAILURE)
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "z!", &ztest) == FAILURE ||
+ redis_extract_auth_info(ztest, &user, &pass) == FAILURE)
{
return FAILURE;
}
- // Construct our AUTH command
- *cmd_len = REDIS_CMD_SPPRINTF(cmd, "AUTH", "s", pw, pw_len);
+ /* Construct either AUTH <user> <pass> or AUTH <pass> */
+ if (user && pass) {
+ *cmd_len = REDIS_CMD_SPPRINTF(cmd, "AUTH", "SS", user, pass);
+ } else {
+ *cmd_len = REDIS_CMD_SPPRINTF(cmd, "AUTH", "S", pass);
+ }
- // Free previously allocated password, and update
- if (redis_sock->auth) zend_string_release(redis_sock->auth);
- redis_sock->auth = zend_string_init(pw, pw_len, 0);
+ redis_sock_set_auth(redis_sock, user, pass);
+
+ if (user) zend_string_release(user);
+ if (pass) zend_string_release(pass);
- // Success
return SUCCESS;
}
diff --git a/redis_commands.h b/redis_commands.h
index addcce53..54bf7ee8 100644
--- a/redis_commands.h
+++ b/redis_commands.h
@@ -23,9 +23,12 @@ typedef struct subscribeContext {
/* Construct a raw command */
int redis_build_raw_cmd(zval *z_args, int argc, char **cmd, int *cmd_len);
+
/* Construct a script command */
smart_string *redis_build_script_cmd(smart_string *cmd, int argc, zval *z_args);
+char *redis_variadic_str_cmd(char *kw, zval *argv, int argc, int *cmd_len);
+
/* Redis command generics. Many commands share common prototypes meaning that
* we can write one function to handle all of them. For example, there are
* many COMMAND key value commands, or COMMAND key commands. */
diff --git a/redis_session.c b/redis_session.c
index e3358148..5428d527 100644
--- a/redis_session.c
+++ b/redis_session.c
@@ -39,6 +39,9 @@
#include "SAPI.h"
#include "ext/standard/url.h"
+#define REDIS_SESSION_PREFIX "PHPREDIS_SESSION:"
+#define CLUSTER_SESSION_PREFIX "PHPREDIS_CLUSTER_SESSION:"
+
/* Session lock LUA as well as its SHA1 hash */
#define LOCK_RELEASE_LUA_STR "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end"
#define LOCK_RELEASE_LUA_LEN (sizeof(LOCK_RELEASE_LUA_STR) - 1)
@@ -49,6 +52,9 @@
#define IS_REDIS_OK(r, len) (r != NULL && len == 3 && !memcmp(r, "+OK", 3))
#define NEGATIVE_LOCK_RESPONSE 1
+#define CLUSTER_DEFAULT_PREFIX() \
+ zend_string_init(CLUSTER_SESSION_PREFIX, sizeof(CLUSTER_SESSION_PREFIX) - 1, 0)
+
ps_module ps_mod_redis = {
PS_MOD_UPDATE_TIMESTAMP(redis)
};
@@ -83,6 +89,9 @@ typedef struct {
} redis_pool;
+// static char *session_conf_string(HashTable *ht, const char *key, size_t keylen) {
+// }
+
PHP_REDIS_API void
redis_pool_add(redis_pool *pool, RedisSock *redis_sock, int weight, int database)
{
@@ -360,12 +369,18 @@ static void lock_release(RedisSock *redis_sock, redis_session_lock_status *lock_
}
}
+#if PHP_VERSION_ID < 70300
+#define REDIS_URL_STR(umem) umem
+#else
+#define REDIS_URL_STR(umem) ZSTR_VAL(umem)
+#endif
+
/* {{{ PS_OPEN_FUNC
*/
PS_OPEN_FUNC(redis)
{
php_url *url;
- zval params, *param;
+ zval params;
int i, j, path_len;
redis_pool *pool = ecalloc(1, sizeof(*pool));
@@ -383,11 +398,10 @@ PS_OPEN_FUNC(redis)
if (i < j) {
int weight = 1;
double timeout = 86400.0, read_timeout = 0.0;
- int persistent = 0;
- int database = -1;
- char *persistent_id = NULL;
- long retry_interval = 0;
- zend_string *prefix = NULL, *auth = NULL;
+ int persistent = 0, db = -1;
+ zend_long retry_interval = 0;
+ zend_string *persistent_id = NULL, *prefix = NULL;
+ zend_string *user = NULL, *pass = NULL;
/* translate unix: into file: */
if (!strncmp(save_path+i, "unix:", sizeof("unix:")-1)) {
@@ -413,51 +427,28 @@ PS_OPEN_FUNC(redis)
/* parse parameters */
if (url->query != NULL) {
+ HashTable *ht;
char *query;
array_init(&params);
-#if (PHP_VERSION_ID < 70300)
- if (url->fragment != NULL) {
- spprintf(&query, 0, "%s#%s", url->query, url->fragment);
- } else {
- query = estrdup(url->query);
- }
-#else
- if (url->fragment != NULL) {
- spprintf(&query, 0, "%s#%s", ZSTR_VAL(url->query), ZSTR_VAL(url->fragment));
+ if (url->fragment) {
+ spprintf(&query, 0, "%s#%s", REDIS_URL_STR(url->query), REDIS_URL_STR(url->fragment));
} else {
- query = estrndup(ZSTR_VAL(url->query), ZSTR_LEN(url->query));
+ query = estrdup(REDIS_URL_STR(url->query));
}
-#endif
- sapi_module.treat_data(PARSE_STRING, query, &params);
- if ((param = zend_hash_str_find(Z_ARRVAL(params), "weight", sizeof("weight") - 1)) != NULL) {
- weight = zval_get_long(param);
- }
- if ((param = zend_hash_str_find(Z_ARRVAL(params), "timeout", sizeof("timeout") - 1)) != NULL) {
- timeout = zval_get_double(param);
- }
- if ((param = zend_hash_str_find(Z_ARRVAL(params), "read_timeout", sizeof("read_timeout") - 1)) != NULL) {
- read_timeout = zval_get_double(param);
- }
- if ((param = zend_hash_str_find(Z_ARRVAL(params), "persistent", sizeof("persistent") - 1)) != NULL) {
- persistent = (atol(Z_STRVAL_P(param)) == 1 ? 1 : 0);
- }
- if ((param = zend_hash_str_find(Z_ARRVAL(params), "persistent_id", sizeof("persistent_id") - 1)) != NULL) {
- persistent_id = estrndup(Z_STRVAL_P(param), Z_STRLEN_P(param));
- }
- if ((param = zend_hash_str_find(Z_ARRVAL(params), "prefix", sizeof("prefix") - 1)) != NULL) {
- prefix = zend_string_init(Z_STRVAL_P(param), Z_STRLEN_P(param), 0);
- }
- if ((param = zend_hash_str_find(Z_ARRVAL(params), "auth", sizeof("auth") - 1)) != NULL) {
- auth = zend_string_init(Z_STRVAL_P(param), Z_STRLEN_P(param), 0);
- }
- if ((param = zend_hash_str_find(Z_ARRVAL(params), "database", sizeof("database") - 1)) != NULL) {
- database = zval_get_long(param);
- }
- if ((param = zend_hash_str_find(Z_ARRVAL(params), "retry_interval", sizeof("retry_interval") - 1)) != NULL) {
- retry_interval = zval_get_long(param);
- }
+ sapi_module.treat_data(PARSE_STRING, query, &params);
+ ht = Z_ARRVAL(params);
+
+ REDIS_CONF_INT_STATIC(ht, "weight", &weight);
+ REDIS_CONF_BOOL_STATIC(ht, "persistent", &persistent);
+ REDIS_CONF_INT_STATIC(ht, "database", &db);
+ REDIS_CONF_DOUBLE_STATIC(ht, "timeout", &timeout);
+ REDIS_CONF_DOUBLE_STATIC(ht, "read_timeout", &read_timeout);
+ REDIS_CONF_LONG_STATIC(ht, "retry_interval", &retry_interval);
+ REDIS_CONF_STRING_STATIC(ht, "persistent_id", &persistent_id);
+ REDIS_CONF_STRING_STATIC(ht, "prefix", &prefix);
+ REDIS_CONF_AUTH_STATIC(ht, "auth", &user, &pass);
zval_dtor(&params);
}
@@ -466,33 +457,38 @@ PS_OPEN_FUNC(redis)
php_url_free(url);
if (persistent_id) efree(persistent_id);
if (prefix) zend_string_release(prefix);
- if (auth) zend_string_release(auth);
+ if (user) zend_string_release(user);
+ if (pass) zend_string_release(pass);
redis_pool_free(pool);
PS_SET_MOD_DATA(NULL);
return FAILURE;
}
RedisSock *redis_sock;
+ char *addr, *scheme;
+ size_t addrlen;
+ int port;
+
+ scheme = url->scheme ? REDIS_URL_STR(url->scheme) : "tcp";
if (url->host) {
- zend_string *address;
-#if (PHP_VERSION_ID < 70300)
- address = strpprintf(0, "%s://%s", url->scheme ? url->scheme : "tcp", url->host);
-#else
- address = strpprintf(0, "%s://%s", url->scheme ? ZSTR_VAL(url->scheme) : "tcp", ZSTR_VAL(url->host));
-#endif
- redis_sock = redis_sock_create(ZSTR_VAL(address), ZSTR_LEN(address), url->port, timeout, read_timeout, persistent, persistent_id, retry_interval);
- zend_string_release(address);
+ port = url->port;
+ addrlen = spprintf(&addr, 0, "%s://%s", scheme, REDIS_URL_STR(url->host));
} else { /* unix */
-#if (PHP_VERSION_ID < 70300)
- redis_sock = redis_sock_create(url->path, strlen(url->path), 0, timeout, read_timeout, persistent, persistent_id, retry_interval);
-#else
- redis_sock = redis_sock_create(ZSTR_VAL(url->path), ZSTR_LEN(url->path), 0, timeout, read_timeout, persistent, persistent_id, retry_interval);
-#endif
+ port = 0;
+ addr = REDIS_URL_STR(url->path);
+ addrlen = strlen(addr);
}
- redis_pool_add(pool, redis_sock, weight, database);
+
+ redis_sock = redis_sock_create(addr, addrlen, port, timeout, read_timeout,
+ persistent, ZSTR_VAL(persistent_id), retry_interval);
+
+ redis_pool_add(pool, redis_sock, weight, db);
redis_sock->prefix = prefix;
- redis_sock->auth = auth;
+ redis_sock_set_auth(redis_sock, user, pass);
+ efree(addr);
+ if (user) zend_string_release(user);
+ if (pass) zend_string_release(pass);
php_url_free(url);
}
}
@@ -534,7 +530,7 @@ static zend_string *
redis_session_key(RedisSock *redis_sock, const char *key, int key_len)
{
zend_string *session;
- char default_prefix[] = "PHPREDIS_SESSION:";
+ char default_prefix[] = REDIS_SESSION_PREFIX;
char *prefix = default_prefix;
size_t prefix_len = sizeof(default_prefix)-1;
@@ -838,42 +834,6 @@ PS_GC_FUNC(redis)
* Redis Cluster session handler functions
*/
-/* Helper to extract timeout values */
-static void session_conf_timeout(HashTable *ht_conf, const char *key, int key_len,
- double *val)
-{
- zval *z_val;
-
- if ((z_val = zend_hash_str_find(ht_conf, key, key_len - 1)) != NULL &&
- Z_TYPE_P(z_val) == IS_STRING
- ) {
- *val = atof(Z_STRVAL_P(z_val));
- }
-}
-
-/* Simple helper to retrieve a boolean (0 or 1) value from a string stored in our
- * session.save_path variable. This is so the user can use 0, 1, or 'true',
- * 'false' */
-static void session_conf_bool(HashTable *ht_conf, char *key, int keylen,
- int *retval) {
- zval *z_val;
- char *str;
- int strlen;
-
- /* See if we have the option, and it's a string */
- if ((z_val = zend_hash_str_find(ht_conf, key, keylen - 1)) != NULL &&
- Z_TYPE_P(z_val) == IS_STRING
- ) {
- str = Z_STRVAL_P(z_val);
- strlen = Z_STRLEN_P(z_val);
-
- /* true/yes/1 are treated as true. Everything else is false */
- *retval = (strlen == 4 && !strncasecmp(str, "true", 4)) ||
- (strlen == 3 && !strncasecmp(str, "yes", 3)) ||
- (strlen == 1 && !strncasecmp(str, "1", 1));
- }
-}
-
/* Prefix a session key */
static char *cluster_session_key(redisCluster *c, const char *key, int keylen,
int *skeylen, short *slot) {
@@ -891,36 +851,31 @@ static char *cluster_session_key(redisCluster *c, const char *key, int keylen,
PS_OPEN_FUNC(rediscluster) {
redisCluster *c;
- zval z_conf, *z_val;
+ zval z_conf, *zv;
HashTable *ht_conf, *ht_seeds;
double timeout = 0, read_timeout = 0;
int persistent = 0, failover = REDIS_FAILOVER_NONE;
- size_t prefix_len, auth_len = 0;
- char *prefix, *auth = NULL;
+ zend_string *prefix = NULL, *user = NULL, *pass = NULL, *failstr = NULL;
/* Parse configuration for session handler */
array_init(&z_conf);
sapi_module.treat_data(PARSE_STRING, estrdup(save_path), &z_conf);
- /* Sanity check that we're able to parse and have a seeds array */
- if (Z_TYPE(z_conf) != IS_ARRAY ||
- (z_val = zend_hash_str_find(Z_ARRVAL(z_conf), "seed", sizeof("seed") - 1)) == NULL ||
- Z_TYPE_P(z_val) != IS_ARRAY)
- {
+ /* We need seeds */
+ zv = REDIS_HASH_STR_FIND_TYPE_STATIC(Z_ARRVAL(z_conf), "seed", IS_ARRAY);
+ if (zv == NULL) {
zval_dtor(&z_conf);
return FAILURE;
}
/* Grab a copy of our config hash table and keep seeds array */
ht_conf = Z_ARRVAL(z_conf);
- ht_seeds = Z_ARRVAL_P(z_val);
+ ht_seeds = Z_ARRVAL_P(zv);
- /* Grab timeouts if they were specified */
- session_conf_timeout(ht_conf, "timeout", sizeof("timeout"), &timeout);
- session_conf_timeout(ht_conf, "read_timeout", sizeof("read_timeout"), &read_timeout);
-
- /* Grab persistent option */
- session_conf_bool(ht_conf, "persistent", sizeof("persistent"), &persistent);
+ /* Optional configuration settings */
+ REDIS_CONF_DOUBLE_STATIC(ht_conf, "timeout", &timeout);
+ REDIS_CONF_DOUBLE_STATIC(ht_conf, "read_timeout", &read_timeout);
+ REDIS_CONF_BOOL_STATIC(ht_conf, "persistent", &persistent);
/* Sanity check on our timeouts */
if (timeout < 0 || read_timeout < 0) {
@@ -930,58 +885,49 @@ PS_OPEN_FUNC(rediscluster) {
return FAILURE;
}
- /* Look for a specific prefix */
- if ((z_val = zend_hash_str_find(ht_conf, "prefix", sizeof("prefix") - 1)) != NULL &&
- Z_TYPE_P(z_val) == IS_STRING && Z_STRLEN_P(z_val) > 0
- ) {
- prefix = Z_STRVAL_P(z_val);
- prefix_len = Z_STRLEN_P(z_val);
- } else {
- prefix = "PHPREDIS_CLUSTER_SESSION:";
- prefix_len = sizeof("PHPREDIS_CLUSTER_SESSION:")-1;
- }
+ REDIS_CONF_STRING_STATIC(ht_conf, "prefix", &prefix);
+ REDIS_CONF_AUTH_STATIC(ht_conf, "auth", &user, &pass);
+ REDIS_CONF_STRING_STATIC(ht_conf, "failover", &failstr);
- /* Look for a specific failover setting */
- if ((z_val = zend_hash_str_find(ht_conf, "failover", sizeof("failover") - 1)) != NULL &&
- Z_TYPE_P(z_val) == IS_STRING && Z_STRLEN_P(z_val) > 0
- ) {
- if (!strcasecmp(Z_STRVAL_P(z_val), "error")) {
+ /* Need to massage failover string if we have it */
+ if (failstr) {
+ if (zend_string_equals_literal_ci(failstr, "error")) {
failover = REDIS_FAILOVER_ERROR;
- } else if (!strcasecmp(Z_STRVAL_P(z_val), "distribute")) {
+ } else if (zend_string_equals_literal_ci(failstr, "distribute")) {
failover = REDIS_FAILOVER_DISTRIBUTE;
}
}
- /* Look for a specific auth setting */
- if ((z_val = zend_hash_str_find(ht_conf, "auth", sizeof("auth") - 1)) != NULL &&
- Z_TYPE_P(z_val) == IS_STRING && Z_STRLEN_P(z_val) > 0
- ) {
- auth = Z_STRVAL_P(z_val);
- auth_len = Z_STRLEN_P(z_val);
- }
-
redisCachedCluster *cc;
zend_string **seeds, *hash = NULL;
uint32_t nseeds;
- /* Extract at least one valid seed or abort */
+ #define CLUSTER_SESSION_CLEANUP() \
+ if (hash) zend_string_release(hash); \
+ if (prefix) zend_string_release(prefix); \
+ if (user) zend_string_release(user); \
+ if (pass) zend_string_release(pass); \
+ free_seed_array(seeds, nseeds); \
+ zval_dtor(&z_conf); \
+
+ /* Extract at least one valid seed or abort */
seeds = cluster_validate_args(timeout, read_timeout, ht_seeds, &nseeds, NULL);
if (seeds == NULL) {
php_error_docref(NULL, E_WARNING, "No valid seeds detected");
+ CLUSTER_SESSION_CLEANUP();
zval_dtor(&z_conf);
return FAILURE;
}
- #define CLUSTER_SESSION_CLEANUP() \
- if (hash) zend_string_release(hash); \
- free_seed_array(seeds, nseeds); \
- zval_dtor(&z_conf); \
-
c = cluster_create(timeout, read_timeout, failover, persistent);
- c->flags->prefix = zend_string_init(prefix, prefix_len, 0);
- if (auth && auth_len)
- c->flags->auth = zend_string_init(auth, auth_len, 0);
+ if (prefix) {
+ c->flags->prefix = zend_string_copy(prefix);
+ } else {
+ c->flags->prefix = CLUSTER_DEFAULT_PREFIX();
+ }
+
+ redis_sock_set_auth(c->flags, user, pass);
/* First attempt to load from cache */
if (CLUSTER_CACHING_ENABLED()) {
diff --git a/tests/RedisArrayTest.php b/tests/RedisArrayTest.php
index 9c53c9ce..a8526886 100644
--- a/tests/RedisArrayTest.php
+++ b/tests/RedisArrayTest.php
@@ -616,7 +616,7 @@ class Redis_Distributor_Test extends TestSuite {
}
}
-function run_tests($className, $str_filter, $str_host, $str_auth) {
+function run_tests($className, $str_filter, $str_host, $auth) {
// reset rings
global $newRing, $oldRing, $serverList;
@@ -625,7 +625,7 @@ function run_tests($className, $str_filter, $str_host, $str_auth) {
$serverList = ["$str_host:6379", "$str_host:6380", "$str_host:6381", "$str_host:6382"];
// run
- return TestSuite::run($className, $str_filter, $str_host, $str_auth);
+ return TestSuite::run($className, $str_filter, $str_host, NULL, $auth);
}
?>
diff --git a/tests/RedisClusterTest.php b/tests/RedisClusterTest.php
index 972c64f6..0da0904c 100644
--- a/tests/RedisClusterTest.php
+++ b/tests/RedisClusterTest.php
@@ -47,6 +47,7 @@ class Redis_Cluster_Test extends Redis_Test {
public function testSwapDB() { return $this->markTestSkipped(); }
public function testConnectException() { return $this->markTestSkipped(); }
public function testTlsConnect() { return $this->markTestSkipped(); }
+ public function testInvalidAuthArgs() { return $this->markTestSkipped(); }
/* Session locking feature is currently not supported in in context of Redis Cluster.
The biggest issue for this is the distribution nature of Redis cluster */
@@ -63,8 +64,8 @@ class Redis_Cluster_Test extends Redis_Test {
public function testSession_lockWaitTime() { return $this->markTestSkipped(); }
/* Load our seeds on construction */
- public function __construct($str_host, $str_auth) {
- parent::__construct($str_host, $str_auth);
+ public function __construct($str_host, $i_port, $str_auth) {
+ parent::__construct($str_host, $i_port, $str_auth);
$str_nodemap_file = dirname($_SERVER['PHP_SELF']) . '/nodes/nodemap';
@@ -688,6 +689,15 @@ class Redis_Cluster_Test extends Redis_Test {
$this->redis->setOption(Redis::OPT_REPLY_LITERAL, false);
}
+ /* Redis and RedisCluster use the same handler for the ACL command but verify we can direct
+ the command to a specific node. */
+ public function testAcl() {
+ if ( ! $this->minVersionCheck("6.0"))
+ return $this->markTestSkipped();
+
+ $this->assertInArray('default', $this->redis->acl('foo', 'USERS'));
+ }
+
public function testSession()
{
@ini_set('session.save_handler', 'rediscluster');
@@ -735,9 +745,11 @@ class Redis_Cluster_Test extends Redis_Test {
*/
protected function getFullHostPath()
{
+ $auth = $this->getAuthFragment();
+
return implode('&', array_map(function ($host) {
return 'seed[]=' . $host;
- }, self::$_arr_node_map)) . ($this->getAuth() ? '&auth=' . $this->getAuth() : '');
+ }, self::$_arr_node_map)) . ($auth ? "&$auth" : '');
}
}
?>
diff --git a/tests/RedisTest.php b/tests/RedisTest.php
index 3cf753d8..4c92e7f6 100644
--- a/tests/RedisTest.php
+++ b/tests/RedisTest.php
@@ -4,8 +4,6 @@ require_once(dirname($_SERVER['PHP_SELF'])."/TestSuite.php");
class Redis_Test extends TestSuite
{
- const PORT = 6379;
-
/* City lat/long */
protected $cities = [
'Chico' => [-121.837478, 39.728494],
@@ -53,11 +51,54 @@ class Redis_Test extends TestSuite
return round(microtime(true)*1000);
}
+ protected function getAuthParts(&$user, &$pass) {
+ $user = $pass = NULL;
+
+ $auth = $this->getAuth();
+ if ( ! $auth)
+ return;
+
+ if (is_array($auth)) {
+ if (count($auth) > 1) {
+ list($user, $pass) = $auth;
+ } else {
+ $pass = $auth[0];
+ }
+ } else {
+ $pass = $auth;
+ }
+ }
+
+ protected function getAuthFragment() {
+ static $_authidx = 0;
+ $_authidx++;
+
+ $this->getAuthParts($user, $pass);
+
+ if ($user && $pass) {
+ if ($_authidx % 2 == 0)
+ return "auth[user]=$user&auth[pass]=$pass";
+ else
+ return "auth[]=$user&auth[]=$pass";
+ } else if ($pass) {
+ if ($_authidx % 3 == 0)
+ return "auth[pass]=$pass";
+ if ($_authidx % 2 == 0)
+ return "auth[]=$pass";
+ else
+ return "auth=$pass";
+ } else {
+ return NULL;
+ }
+ }
+
protected function getFullHostPath()
{
$fullHostPath = parent::getFullHostPath();
- if (isset($fullHostPath) && $this->getAuth()) {
- $fullHostPath .= '?auth=' . $this->getAuth();
+ $authFragment = $this->getAuthFragment();
+
+ if (isset($fullHostPath) && $authFragment) {
+ $fullHostPath .= "?$authFragment";
}
return $fullHostPath;
}
@@ -65,7 +106,7 @@ class Redis_Test extends TestSuite
protected function newInstance() {
$r = new Redis();
- $r->connect($this->getHost(), self::PORT);
+ $r->connect($this->getHost(), $this->getPort());
if($this->getAuth()) {
$this->assertTrue($r->auth($this->getAuth()));
@@ -4958,7 +4999,7 @@ class Redis_Test extends TestSuite
public function testIntrospection() {
// Simple introspection tests
$this->assertTrue($this->redis->getHost() === $this->getHost());
- $this->assertTrue($this->redis->getPort() === self::PORT);
+ $this->assertTrue($this->redis->getPort() === $this->getPort());
$this->assertTrue($this->redis->getAuth() === $this->getAuth());
}
@@ -5435,7 +5476,6 @@ class Redis_Test extends TestSuite
$ret2 = $this->redis->$cmd('{gk}', $city, 500, 'mi', $realopts);
}
- if ($ret1 != $ret2) die();
$this->assertEquals($ret1, $ret2);
}
}
@@ -6024,6 +6064,100 @@ class Redis_Test extends TestSuite
}
}
+ public function testInvalidAuthArgs() {
+ $obj_new = $this->newInstance();
+
+ $arr_args = [
+ [],
+ [NULL, NULL],
+ ['foo', 'bar', 'baz'],
+ ['a','b','c','d'],
+ ['a','b','c'],
+ [['a','b'], 'a'],
+ [['a','b','c']],
+ [[NULL, 'pass']],
+ [[NULL, NULL]],
+ ];
+
+ foreach ($arr_args as $arr_arg) {
+ try {
+ @call_user_func_array([$obj_new, 'auth'], $arr_arg);
+ } catch (Exception $ex) {
+ unset($ex); /* Suppress intellisense warning */
+ }
+ }
+ }
+
+ public function testAcl() {
+ if ( ! $this->minVersionCheck("6.0"))
+ return $this->markTestSkipped();
+
+ /* ACL USERS/SETUSER */
+ $this->assertTrue(in_array('default', $this->redis->acl('USERS')));
+ $this->assertTrue($this->redis->acl('SETUSER', 'admin', 'on', '>admin', '+@all'));
+ $this->assertTrue($this->redis->acl('SETUSER', 'noperm', 'on', '>noperm', '-@all'));
+ $this->assertInArray('default', $this->redis->acl('USERS'));
+
+ /* Verify ACL GETUSER has the correct hash and is in 'nice' format */
+ $arr_admin = $this->redis->acl('GETUSER', 'admin');
+ $this->assertInArray(hash('sha256', 'admin'), $arr_admin['passwords']);
+
+ /* Now nuke our 'admin' user and make sure it went away */
+ $this->assertTrue($this->redis->acl('DELUSER', 'admin'));
+ $this->assertTrue(!in_array('admin', $this->redis->acl('USERS')));
+
+ /* Try to log in with a bad username/password */
+ $this->assertThrowsMatch($this->redis,
+ function($o) { $o->auth(['1337haxx00r', 'lolwut']); }, '/^WRONGPASS.*$/');
+
+ /* We attempted a bad login. We should have an ACL log entry */
+ $arr_log = $this->redis->acl('log');
+ if (! $arr_log || !is_array($arr_log)) {
+ $this->assertTrue(false);
+ return;
+ }
+
+ /* Make sure our ACL LOG entries are nice for the user */
+ $arr_entry = array_shift($arr_log);
+ $this->assertArrayKey($arr_entry, 'age-seconds', 'is_numeric');
+ $this->assertArrayKey($arr_entry, 'count', 'is_int');
+
+ /* ACL CAT */
+ $cats = $this->redis->acl('CAT');
+ foreach (['read', 'write', 'slow'] as $cat) {
+ $this->assertInArray($cat, $cats);
+ }
+
+ /* ACL CAT <string> */
+ $cats = $this->redis->acl('CAT', 'string');
+ foreach (['get', 'set', 'setnx'] as $cat) {
+ $this->assertInArray($cat, $cats);
+ }
+
+ /* ctype_xdigit even if PHP doesn't have it */
+ $ctype_xdigit = function($v) {
+ if (function_exists('ctype_xdigit')) {
+ return ctype_xdigit($v);
+ } else {
+ return strspn(strtoupper($v), '0123456789ABCDEF') == strlen($v);
+ }
+ };
+
+ /* ACL GENPASS/ACL GENPASS <bits> */
+ $this->assertValidate($this->redis->acl('GENPASS'), $ctype_xdigit);
+ $this->assertValidate($this->redis->acl('GENPASS', 1024), $ctype_xdigit);
+
+ /* ACL WHOAMI */
+ $this->assertValidate($this->redis->acl('WHOAMI'), 'strlen');
+
+ /* Finally make sure AUTH errors throw an exception */
+ $r2 = $this->newInstance(true);
+
+ /* Test NOPERM exception */
+ $this->assertTrue($r2->auth(['noperm', 'noperm']));
+ $this->assertThrowsMatch($r2, function($r) { $r->set('foo', 'bar'); }, '/^NOPERM.*$/');
+ }
+
/* If we detect a unix socket make sure we can connect to it in a variety of ways */
public function testUnixSocket() {
if ( ! file_exists("/tmp/redis.sock")) {
@@ -6285,11 +6419,15 @@ class Redis_Test extends TestSuite
public function testTlsConnect()
{
+ if (($fp = @fsockopen($this->getHost(), 6378)) == NULL)
+ return $this->markTestSkipped();
+
+ fclose($fp);
+
foreach (['localhost' => true, '127.0.0.1' => false] as $host => $verify) {
$redis = new Redis();
$this->assertTrue($redis->connect('tls://' . $host, 6378, 0, null, 0, 0, [
- 'verify_peer_name' => $verify,
- 'verify_peer' => false,
+ 'stream' => ['verify_peer_name' => $verify, 'verify_peer' => false]
]));
}
}
diff --git a/tests/TestRedis.php b/tests/TestRedis.php
index e6f70ca8..843a28f8 100644
--- a/tests/TestRedis.php
+++ b/tests/TestRedis.php
@@ -11,7 +11,7 @@ error_reporting(E_ALL);
ini_set( 'display_errors','1');
/* Grab options */
-$arr_args = getopt('', ['host:', 'class:', 'test:', 'nocolors', 'auth:']);
+$arr_args = getopt('', ['host:', 'port:', 'class:', 'test:', 'nocolors', 'user:', 'auth:']);
/* Grab the test the user is trying to run */
$arr_valid_classes = ['redis', 'redisarray', 'rediscluster', 'redissentinel'];
@@ -21,12 +21,24 @@ $boo_colorize = !isset($arr_args['nocolors']);
/* Get our test filter if provided one */
$str_filter = isset($arr_args['test']) ? $arr_args['test'] : NULL;
-/* Grab override test host if it was passed */
+/* Grab override host/port if it was passed */
$str_host = isset($arr_args['host']) ? $arr_args['host'] : '127.0.0.1';
+$i_port = isset($arr_args['port']) ? intval($arr_args['port']) : 6379;
-/* Grab redis authentication password */
+/* Get optional username and auth (password) */
+$str_user = isset($arr_args['user']) ? $arr_args['user'] : NULL;
$str_auth = isset($arr_args['auth']) ? $arr_args['auth'] : NULL;
+/* Massage the actual auth arg */
+$auth = NULL;
+if ($str_user && $str_auth) {
+ $auth = [$str_user, $str_auth];
+} else if ($str_auth) {
+ $auth = $str_auth;
+} else if ($str_user) {
+ echo TestSuite::make_warning("User passed without a password, ignoring!\n");
+}
+
/* Validate the class is known */
if (!in_array($str_class, $arr_valid_classes)) {
echo "Error: Valid test classes are Redis, RedisArray, RedisCluster and RedisSentinel!\n";
@@ -44,7 +56,7 @@ echo "Using PHP version " . PHP_VERSION . " (" . (PHP_INT_SIZE*8) . " bits)\n";
echo "Testing class ";
if ($str_class == 'redis') {
echo TestSuite::make_bold("Redis") . "\n";
- exit(TestSuite::run("Redis_Test", $str_filter, $str_host, $str_auth));
+ exit(TestSuite::run("Redis_Test", $str_filter, $str_host, $i_port, $auth));
} else if ($str_class == 'redisarray') {
echo TestSuite::make_bold("RedisArray") . "\n";
global $useIndex;
@@ -52,19 +64,23 @@ if ($str_class == 'redis') {
echo "\n".($useIndex?"WITH":"WITHOUT"). " per-node index:\n";
/* The various RedisArray subtests we can run */
- $arr_ra_tests = ['Redis_Array_Test', 'Redis_Rehashing_Test', 'Redis_Auto_Rehashing_Test', 'Redis_Multi_Exec_Test', 'Redis_Distributor_Test'];
+ $arr_ra_tests = [
+ 'Redis_Array_Test', 'Redis_Rehashing_Test', 'Redis_Auto_Rehashing_Test',
+ 'Redis_Multi_Exec_Test', 'Redis_Distributor_Test'
+ ];
+
foreach ($arr_ra_tests as $str_test) {
/* Run until we encounter a failure */
- if (run_tests($str_test, $str_filter, $str_host, $str_auth) != 0) {
+ if (run_tests($str_test, $str_filter, $str_host, $auth) != 0) {
exit(1);
}
}
}
} else if ($str_class == 'rediscluster') {
echo TestSuite::make_bold("RedisCluster") . "\n";
- exit(TestSuite::run("Redis_Cluster_Test", $str_filter, $str_host, $str_auth));
+ exit(TestSuite::run("Redis_Cluster_Test", $str_filter, $str_host, $i_port, $auth));
} else {
echo TestSuite::make_bold("RedisSentinel") . "\n";
- exit(TestSuite::run("Redis_Sentinel_Test", $str_filter, $str_host, $str_auth));
+ exit(TestSuite::run("Redis_Sentinel_Test", $str_filter, $str_host, $i_port, $auth));
}
?>
diff --git a/tests/TestSuite.php b/tests/TestSuite.php
index 24cfed60..c879b330 100644
--- a/tests/TestSuite.php
+++ b/tests/TestSuite.php
@@ -6,11 +6,12 @@ class TestSkippedException extends Exception {}
// phpunit is such a pain to install, we're going with pure-PHP here.
class TestSuite
{
- /* Host the tests will use */
+ /* Host and port the unit tests will use */
private $str_host;
+ private $i_port = 6379;
/* Redis authentication we'll use */
- private $str_auth;
+ private $auth;
private static $_boo_colorize = false;
@@ -28,13 +29,15 @@ class TestSuite
public static $errors = [];
public static $warnings = [];
- public function __construct($str_host, $str_auth) {
+ public function __construct($str_host, $i_port, $auth) {
$this->str_host = $str_host;
- $this->str_auth = $str_auth;
+ $this->i_port = $i_port;
+ $this->auth = $auth;
}
public function getHost() { return $this->str_host; }
- public function getAuth() { return $this->str_auth; }
+ public function getPort() { return $this->i_port; }
+ public function getAuth() { return $this->auth; }
/**
* Returns the fully qualified host path,
@@ -45,7 +48,7 @@ class TestSuite
protected function getFullHostPath()
{
return $this->str_host
- ? 'tcp://' . $this->str_host . ':6379'
+ ? 'tcp://' . $this->str_host . ':' . $this->i_port
: null;
}
@@ -95,6 +98,75 @@ class TestSuite
return false;
}
+ protected function assertInArray($ele, $arr, $cb = NULL) {
+ if ($cb && !is_callable($cb))
+ die("Fatal: assertInArray callback must be callable!\n");
+
+ if (($in = in_array($ele, $arr)) && (!$cb || $cb($arr[array_search($ele, $arr)])))
+ return true;
+
+
+ $bt = debug_backtrace(false);
+ $ex = $in ? 'validation' : 'missing';
+ self::$errors []= sprintf("Assertion failed: %s:%d (%s) [%s '%s']\n",
+ $bt[0]["file"], $bt[0]["line"], $bt[1]["function"], $ex, $ele);
+
+ return false;
+ }
+
+ protected function assertArrayKey($arr, $key, $cb = NULL) {
+ if ($cb && !is_callable($cb))
+ die("Fatal: assertArrayKey callback must be callable\n");
+
+ if (($exists = isset($arr[$key])) && (!$cb || $cb($arr[$key])))
+ return true;
+
+ $bt = debug_backtrace(false);
+ $ex = $exists ? 'validation' : 'missing';
+ self::$errors []= sprintf("Assertion failed: %s:%d (%s) [%s '%s']\n",
+ $bt[0]["file"], $bt[0]["line"], $bt[1]["function"], $ex, $key);
+
+ return false;
+ }
+
+ protected function assertValidate($val, $cb) {
+ if ( ! is_callable($cb))
+ die("Fatal: Callable assertValidate callback required\n");
+
+ if ($cb($val))
+ return true;
+
+ $bt = debug_backtrace(false);
+ self::$errors []= sprintf("Assertion failed: %s:%d (%s)\n",
+ $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]);
+
+ return false;
+ }
+
+ protected function assertThrowsMatch($arg, $cb, $regex = NULL) {
+ $threw = $match = false;
+
+ if ( ! is_callable($cb))
+ die("Fatal: Callable assertThrows callback required\n");
+
+ try {
+ $cb($arg);
+ } catch (Exception $ex) {
+ $threw = true;
+ $match = !$regex || preg_match($regex, $ex->getMessage());
+ }
+
+ if ($threw && $match)
+ return true;
+
+ $bt = debug_backtrace(false);
+ $ex = !$threw ? 'no exception' : "no match '$regex'";
+ self::$errors []= sprintf("Assertion failed: %s:%d (%s) [%s]\n",
+ $bt[0]["file"], $bt[0]["line"], $bt[1]["function"], $ex);
+
+ return false;
+ }
+
protected function assertLess($a, $b) {
if($a < $b)
return;
@@ -157,7 +229,7 @@ class TestSuite
posix_isatty(STDOUT);
}
- public static function run($className, $str_limit = NULL, $str_host = NULL, $str_auth = NULL) {
+ public static function run($className, $str_limit = NULL, $str_host = NULL, $i_port = NULL, $auth = NULL) {
/* Lowercase our limit arg if we're passed one */
$str_limit = $str_limit ? strtolower($str_limit) : $str_limit;
@@ -181,7 +253,7 @@ class TestSuite
echo self::make_bold($str_out_name);
$count = count($className::$errors);
- $rt = new $className($str_host, $str_auth);
+ $rt = new $className($str_host, $i_port, $auth);
try {
$rt->setUp();
diff --git a/tests/make-cluster.sh b/tests/make-cluster.sh
index b3a91086..244cc59a 100755
--- a/tests/make-cluster.sh
+++ b/tests/make-cluster.sh
@@ -15,6 +15,7 @@ MAPFILE=$NODEDIR/nodemap
# Host, nodes, replicas, ports, etc. Change if you want different values
HOST="127.0.0.1"
+NOASK=0
NODES=12
REPLICAS=3
START_PORT=7000
@@ -36,10 +37,15 @@ verboseRun() {
# Spawn a specific redis instance, cluster enabled
spawnNode() {
+ # ACL file if we have one
+ if [ ! -z "$ACLFILE" ]; then
+ ACLARG="--aclfile $ACLFILE"
+ fi
+
# Attempt to spawn the node
verboseRun redis-server --cluster-enabled yes --dir $NODEDIR --port $PORT \
--cluster-config-file node-$PORT.conf --daemonize yes --save \'\' \
- --bind $HOST --dbfilename node-$PORT.rdb
+ --bind $HOST --dbfilename node-$PORT.rdb $ACLARG
# Abort if we can't spin this instance
if [ $? -ne 0 ]; then
@@ -80,6 +86,10 @@ checkNodes() {
cleanConfigInfo() {
verboseRun mkdir -p $NODEDIR
verboseRun rm -f $NODEDIR/*
+
+ if [ -f "$ACLFILE" ]; then
+ cp $ACLFILE $NODEDIR/$ACLFILE
+ fi
}
# Initialize our cluster with redis-trib.rb
@@ -89,7 +99,18 @@ initCluster() {
TRIBARGS="$TRIBARGS $HOST:$PORT"
done
- verboseRun redis-trib.rb create --replicas $REPLICAS $TRIBARGS
+ if [[ ! -z "$USER" ]]; then
+ USERARG="--user $USER"
+ fi
+ if [[ ! -z "$PASS" ]]; then
+ PASSARG="-a $PASS"
+ fi
+
+ if [[ "$1" -eq "1" ]]; then
+ echo yes | redis-cli $USERARG $PASSARG -p $START_PORT --cluster create $TRIBARGS --cluster-replicas $REPLICAS
+ else
+ verboseRun redis-cli $USERARG $PASSARG -p $START_PORT --cluster create $TRIBARGS --cluster-replicas $REPLICAS
+ fi
if [ $? -ne 0 ]; then
echo "Error: Couldn't create cluster!"
@@ -109,7 +130,7 @@ startCluster() {
spawnNodes
# Attempt to initialize the cluster
- initCluster
+ initCluster $1
}
# Shut down nodes in our cluster
@@ -119,24 +140,86 @@ stopCluster() {
done
}
-# Make sure we have redis-server and redis-trib.rb on the path
+# Shut down nodes by killing them
+killCluster() {
+ for PORT in `seq $START_PORT $END_PORT`; do
+ PID=$(ps aux|grep [r]edis-server|grep $PORT|awk '{print $2}')
+ echo -n "Killing $PID: "
+ if kill $PID; then
+ echo "OK"
+ else
+ echo "ERROR"
+ fi
+ done
+}
+
+printUsage() {
+ echo "Usage: make-cluster [OPTIONS] <start|stop|kill>"
+ echo
+ echo " Options"
+ echo
+ echo " -u Redis username to use when spawning cluster"
+ echo " -p Redis password to use when spawning cluster"
+ echo " -a Redis acl filename to use when spawning cluster"
+ echo " -y Automatically send 'yes' when starting cluster"
+ echo " -h This message"
+ echo
+ exit 0
+}
+
+# We need redis-server
checkExe redis-server
-checkExe redis-trib.rb
-# Override the host if we've got $2
-if [[ ! -z "$2" ]]; then
- HOST=$2
+while getopts "u:p:a:hy" OPT; do
+ case $OPT in
+ h)
+ printUsage
+ ;;
+ a)
+ if [ ! -f "$OPTARG" ]; then
+ echo "Error: '$OPTARG' is not a filename!"
+ exit -1
+ fi
+ ACLFILE=$OPTARG
+ ;;
+ u)
+ USER=$OPTARG
+ ;;
+ p)
+ PASS=$OPTARG
+ ;;
+ h)
+ HOST=$OPTARG
+ ;;
+ y)
+ NOASK=1
+ ;;
+ *)
+ echo "Unknown option: $OPT"
+ exit 1
+ ;;
+ esac
+done
+
+shift "$((OPTIND - 1))"
+
+if [[ $# -lt 1 ]]; then
+ echo "Error: Must pass an operation (start or stop)"
+ exit -1
fi
-# Main entry point to start or stop/kill a cluster
case "$1" in
start)
- startCluster
+ startCluster $NOASK
;;
stop)
stopCluster
;;
+ kill)
+ killCluster
+ ;;
*)
- echo "Usage $0 <start|stop> [host]"
+ echo "Usage: make-cluster.sh [options] <start|stop>"
+ exit 1
;;
esac
diff --git a/tests/startSession.php b/tests/startSession.php
index 2149da68..c0ae1884 100644
--- a/tests/startSession.php
+++ b/tests/startSession.php
@@ -38,4 +38,4 @@ if (!empty($sessionData)) {
}
session_write_close();
-echo $sessionStartSuccessful ? 'SUCCESS' : 'FAILURE'; \ No newline at end of file
+echo $sessionStartSuccessful ? 'SUCCESS' : 'FAILURE';
diff --git a/tests/users.acl b/tests/users.acl
new file mode 100644
index 00000000..f3518d33
--- /dev/null
+++ b/tests/users.acl
@@ -0,0 +1,2 @@
+user default on >phpredis ~* +@all
+user phpredis on >phpredis ~* +@all