1 /* -*- Mode: C; tab-width: 4 -*- */
2 /*
3   +----------------------------------------------------------------------+
4   | Copyright (c) 1997-2009 The PHP Group                                |
5   +----------------------------------------------------------------------+
6   | This source file is subject to version 3.01 of the PHP license,      |
7   | that is bundled with this package in the file LICENSE, and is        |
8   | available through the world-wide-web at the following url:           |
9   | http://www.php.net/license/3_01.txt                                  |
10   | If you did not receive a copy of the PHP license and are unable to   |
11   | obtain it through the world-wide-web, please send a note to          |
12   | license@php.net so we can mail you a copy immediately.               |
13   +----------------------------------------------------------------------+
14   | Original Author: Michael Grunder <michael.grunder@gmail.com          |
15   | Maintainer: Nicolas Favre-Felix <n.favre-felix@owlient.eu>           |
16   +----------------------------------------------------------------------+
17 */
18 
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 
23 #include "redis_commands.h"
24 
25 #include "php_network.h"
26 
27 #ifndef PHP_WIN32
28 #include <netinet/tcp.h> /* TCP_KEEPALIVE */
29 #else
30 #include <winsock.h>
31 #endif
32 
33 #include <zend_exceptions.h>
34 
35 /* Georadius sort type */
36 typedef enum geoSortType {
37     SORT_NONE,
38     SORT_ASC,
39     SORT_DESC
40 } geoSortType;
41 
42 /* Georadius store type */
43 typedef enum geoStoreType {
44     STORE_NONE,
45     STORE_COORD,
46     STORE_DIST
47 } geoStoreType;
48 
49 /* Georadius options structure */
50 typedef struct geoOptions {
51     int withcoord;
52     int withdist;
53     int withhash;
54     long count;
55     geoSortType sort;
56     geoStoreType store;
57     zend_string *key;
58 } geoOptions;
59 
60 /* Local passthrough macro for command construction.  Given that these methods
61  * are generic (so they work whether the caller is Redis or RedisCluster) we
62  * will always have redis_sock, slot*, and */
63 #define REDIS_CMD_SPPRINTF(ret, kw, fmt, ...) \
64     redis_spprintf(redis_sock, slot, ret, kw, fmt, ##__VA_ARGS__)
65 
66 /* Generic commands based on method signature and what kind of things we're
67  * processing.  Lots of Redis commands take something like key, value, or
68  * key, value long.  Each unique signature like this is written only once */
69 
70 /* A command that takes no arguments */
redis_empty_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)71 int redis_empty_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
72                     char *kw, char **cmd, int *cmd_len, short *slot,
73                     void **ctx)
74 {
75     *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "");
76     return SUCCESS;
77 }
78 
79 /* Helper to construct a raw command.  Given that the cluster and non cluster
80  * versions are different (RedisCluster needs an additional argument to direct
81  * the command) we take the start of our array and count */
redis_build_raw_cmd(zval * z_args,int argc,char ** cmd,int * cmd_len)82 int redis_build_raw_cmd(zval *z_args, int argc, char **cmd, int *cmd_len)
83 {
84     smart_string cmdstr = {0};
85     int i;
86 
87     /* Make sure our first argument is a string */
88     if (Z_TYPE(z_args[0]) != IS_STRING) {
89         php_error_docref(NULL, E_WARNING,
90             "When sending a 'raw' command, the first argument must be a string!");
91         return FAILURE;
92     }
93 
94     /* Initialize our command string */
95     redis_cmd_init_sstr(&cmdstr, argc-1, Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0]));
96 
97     for (i = 1; i < argc; i++) {
98        switch (Z_TYPE(z_args[i])) {
99             case IS_STRING:
100                 redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[i]),
101                     Z_STRLEN(z_args[i]));
102                 break;
103             case IS_LONG:
104                 redis_cmd_append_sstr_long(&cmdstr,Z_LVAL(z_args[i]));
105                 break;
106             case IS_DOUBLE:
107                 redis_cmd_append_sstr_dbl(&cmdstr,Z_DVAL(z_args[i]));
108                 break;
109             default:
110                 php_error_docref(NULL, E_WARNING,
111                     "Raw command arguments must be scalar values!");
112                 efree(cmdstr.c);
113                 return FAILURE;
114         }
115     }
116 
117     /* Push command and length to caller */
118     *cmd = cmdstr.c;
119     *cmd_len = cmdstr.len;
120 
121     return SUCCESS;
122 }
123 
124 smart_string *
redis_build_script_cmd(smart_string * cmd,int argc,zval * z_args)125 redis_build_script_cmd(smart_string *cmd, int argc, zval *z_args)
126 {
127     int i;
128     zend_string *zstr;
129 
130     if (Z_TYPE(z_args[0]) != IS_STRING) {
131         return NULL;
132     }
133     // Branch based on the directive
134     if (!strcasecmp(Z_STRVAL(z_args[0]), "flush") || !strcasecmp(Z_STRVAL(z_args[0]), "kill")) {
135         // Simple SCRIPT FLUSH, or SCRIPT_KILL command
136         REDIS_CMD_INIT_SSTR_STATIC(cmd, argc, "SCRIPT");
137         redis_cmd_append_sstr(cmd, Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0]));
138     } else if (!strcasecmp(Z_STRVAL(z_args[0]), "load")) {
139         // Make sure we have a second argument, and it's not empty.  If it is
140         // empty, we can just return an empty array (which is what Redis does)
141         if (argc < 2 || Z_TYPE(z_args[1]) != IS_STRING || Z_STRLEN(z_args[1]) < 1) {
142             return NULL;
143         }
144         // Format our SCRIPT LOAD command
145         REDIS_CMD_INIT_SSTR_STATIC(cmd, argc, "SCRIPT");
146         redis_cmd_append_sstr(cmd, "LOAD", sizeof("LOAD") - 1);
147         redis_cmd_append_sstr(cmd, Z_STRVAL(z_args[1]), Z_STRLEN(z_args[1]));
148     } else if (!strcasecmp(Z_STRVAL(z_args[0]), "exists")) {
149         // Make sure we have a second argument
150         if (argc < 2) {
151             return NULL;
152         }
153         /* Construct our SCRIPT EXISTS command */
154         REDIS_CMD_INIT_SSTR_STATIC(cmd, argc, "SCRIPT");
155         redis_cmd_append_sstr(cmd, "EXISTS", sizeof("EXISTS") - 1);
156 
157         for (i = 1; i < argc; ++i) {
158             zstr = zval_get_string(&z_args[i]);
159             redis_cmd_append_sstr(cmd, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
160             zend_string_release(zstr);
161         }
162     } else {
163         /* Unknown directive */
164         return NULL;
165     }
166     return cmd;
167 }
168 
169 /* Command that takes one optional string */
redis_opt_str_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)170 int redis_opt_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw,
171                       char **cmd, int *cmd_len, short *slot, void **ctx)
172 {
173     char *arg = NULL;
174     size_t arglen;
175 
176     if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s!", &arg, &arglen) == FAILURE) {
177         return FAILURE;
178     }
179 
180     if (arg != NULL) {
181         *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "s", arg, arglen);
182     } else {
183         *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "");
184     }
185 
186     return SUCCESS;
187 }
188 
189 /* Generic command where we just take a string and do nothing to it*/
redis_str_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)190 int redis_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw,
191                   char **cmd, int *cmd_len, short *slot, void **ctx)
192 {
193     char *arg;
194     size_t arg_len;
195 
196     // Parse args
197     if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len)
198                              ==FAILURE)
199     {
200         return FAILURE;
201     }
202 
203     // Build the command without molesting the string
204     *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "s", arg, arg_len);
205 
206     return SUCCESS;
207 }
208 
209 /* Key, long, zval (serialized) */
redis_key_long_val_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)210 int redis_key_long_val_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
211                            char *kw, char **cmd, int *cmd_len, short *slot,
212                            void **ctx)
213 {
214     char *key = NULL;
215     size_t key_len;
216     zend_long expire;
217     zval *z_val;
218 
219     if (zend_parse_parameters(ZEND_NUM_ARGS(), "slz", &key, &key_len,
220                              &expire, &z_val) == FAILURE)
221     {
222         return FAILURE;
223     }
224 
225     *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "klv", key, key_len, expire, z_val);
226 
227     return SUCCESS;
228 }
229 
230 /* Generic key, long, string (unserialized) */
redis_key_long_str_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)231 int redis_key_long_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
232                            char *kw, char **cmd, int *cmd_len, short *slot,
233                            void **ctx)
234 {
235     char *key, *val;
236     size_t key_len, val_len;
237     zend_long lval;
238 
239     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sls", &key, &key_len,
240                              &lval, &val, &val_len) == FAILURE)
241     {
242         return FAILURE;
243     }
244 
245     *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kds", key, key_len, (int)lval, val, val_len);
246 
247     return SUCCESS;
248 }
249 
250 /* Generic command construction when we just take a key and value */
redis_kv_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)251 int redis_kv_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
252                  char *kw, char **cmd, int *cmd_len, short *slot,
253                  void **ctx)
254 {
255     char *key;
256     size_t key_len;
257     zval *z_val;
258 
259     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz", &key, &key_len,
260                              &z_val) == FAILURE)
261     {
262         return FAILURE;
263     }
264 
265     *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kv", key, key_len, z_val);
266 
267     return SUCCESS;
268 }
269 
270 /* Generic command that takes a key and an unserialized value */
redis_key_str_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)271 int redis_key_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
272                       char *kw, char **cmd, int *cmd_len, short *slot,
273                       void **ctx)
274 {
275     char *key, *val;
276     size_t key_len, val_len;
277 
278     if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &key, &key_len,
279                              &val, &val_len) == FAILURE)
280     {
281         return FAILURE;
282     }
283 
284     // Construct command
285     *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ks", key, key_len, val, val_len);
286 
287     return SUCCESS;
288 }
289 
290 /* Key, string, string without serialization (ZCOUNT, ZREMRANGEBYSCORE) */
redis_key_str_str_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)291 int redis_key_str_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
292                           char *kw, char **cmd, int *cmd_len, short *slot,
293                           void **ctx)
294 {
295     char *k, *v1, *v2;
296     size_t klen, v1len, v2len;
297 
298     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss", &k, &klen,
299                              &v1, &v1len, &v2, &v2len) == FAILURE)
300     {
301         return FAILURE;
302     }
303 
304     *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", k, klen, v1, v1len, v2, v2len);
305 
306     // Success!
307     return SUCCESS;
308 }
309 
310 /* Generic command that takes two keys */
redis_key_key_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)311 int redis_key_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
312                       char *kw, char **cmd, int *cmd_len, short *slot,
313                       void **ctx)
314 {
315     char *k1, *k2;
316     size_t k1len, k2len;
317     int k1free, k2free;
318 
319     if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &k1, &k1len,
320                              &k2, &k2len) == FAILURE)
321     {
322         return FAILURE;
323     }
324 
325     // Prefix both keys
326     k1free = redis_key_prefix(redis_sock, &k1, &k1len);
327     k2free = redis_key_prefix(redis_sock, &k2, &k2len);
328 
329     // If a slot is requested, we can test that they hash the same
330     if (slot) {
331         // Slots where these keys resolve
332         short slot1 = cluster_hash_key(k1, k1len);
333         short slot2 = cluster_hash_key(k2, k2len);
334 
335         // Check if Redis would give us a CROSSLOT error
336         if (slot1 != slot2) {
337             php_error_docref(0, E_WARNING, "Keys don't hash to the same slot");
338             if (k1free) efree(k1);
339             if (k2free) efree(k2);
340             return FAILURE;
341         }
342 
343         // They're both the same
344         *slot = slot1;
345     }
346 
347     /* Send keys as normal strings because we manually prefixed to check against
348      * cross slot error. */
349     *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ss", k1, k1len, k2, k2len);
350 
351     /* Clean keys up if we prefixed */
352     if (k1free) efree(k1);
353     if (k2free) efree(k2);
354 
355     return SUCCESS;
356 }
357 
358 /* Generic command construction where we take a key and a long */
redis_key_long_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)359 int redis_key_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
360                        char *kw, char **cmd, int *cmd_len, short *slot,
361                        void **ctx)
362 {
363     char *key;
364     size_t keylen;
365     zend_long lval;
366 
367     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl", &key, &keylen, &lval)
368                               ==FAILURE)
369     {
370         return FAILURE;
371     }
372 
373     *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kl", key, keylen, lval);
374 
375     // Success!
376     return SUCCESS;
377 }
378 
379 /* long, long */
redis_long_long_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)380 int redis_long_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
381                         char *kw, char **cmd, int *cmd_len, short *slot,
382                         void **ctx)
383 {
384     zend_long v1, v2;
385 
386     if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &v1, &v2)
387                               == FAILURE)
388     {
389         return FAILURE;
390     }
391 
392     *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ll", v1, v2);
393 
394     return SUCCESS;
395 }
396 
397 /* key, long, long */
redis_key_long_long_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)398 int redis_key_long_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
399                             char *kw, char **cmd, int *cmd_len, short *slot,
400                             void **ctx)
401 {
402     char *key;
403     size_t key_len;
404     zend_long val1, val2;
405 
406     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sll", &key, &key_len,
407                              &val1, &val2) == FAILURE)
408     {
409         return FAILURE;
410     }
411 
412     *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kll", key, key_len, val1, val2);
413 
414     return SUCCESS;
415 }
416 
417 /* Generic command where we take a single key */
redis_key_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)418 int redis_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
419                   char *kw, char **cmd, int *cmd_len, short *slot,
420                   void **ctx)
421 {
422     char *key;
423     size_t key_len;
424 
425     if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &key, &key_len)
426                              ==FAILURE)
427     {
428         return FAILURE;
429     }
430 
431     *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "k", key, key_len);
432 
433     return SUCCESS;
434 }
435 
redis_flush_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)436 int redis_flush_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
437                char *kw, char **cmd, int *cmd_len, short *slot, void **ctx)
438 {
439     zend_bool async = 0;
440 
441     if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &async) == FAILURE) {
442         return FAILURE;
443     }
444 
445     if (async) {
446         *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "s", "ASYNC", sizeof("ASYNC") - 1);
447     } else {
448         *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "");
449     }
450 
451     return SUCCESS;
452 }
453 
454 /* Generic command where we take a key and a double */
redis_key_dbl_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)455 int redis_key_dbl_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
456                       char *kw, char **cmd, int *cmd_len, short *slot,
457                       void **ctx)
458 {
459     char *key;
460     size_t key_len;
461     double val;
462 
463     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sd", &key, &key_len,
464                              &val) == FAILURE)
465     {
466         return FAILURE;
467     }
468 
469     *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kf", key, key_len, val);
470 
471     return SUCCESS;
472 }
473 
474 /* Generic to construct SCAN and variant commands */
redis_fmt_scan_cmd(char ** cmd,REDIS_SCAN_TYPE type,char * key,int key_len,long it,char * pat,int pat_len,long count)475 int redis_fmt_scan_cmd(char **cmd, REDIS_SCAN_TYPE type, char *key, int key_len,
476                        long it, char *pat, int pat_len, long count)
477 {
478     static char *kw[] = {"SCAN","SSCAN","HSCAN","ZSCAN"};
479     int argc;
480     smart_string cmdstr = {0};
481 
482     // Figure out our argument count
483     argc = 1 + (type!=TYPE_SCAN) + (pat_len>0?2:0) + (count>0?2:0);
484 
485     redis_cmd_init_sstr(&cmdstr, argc, kw[type], strlen(kw[type]));
486 
487     // Append our key if it's not a regular SCAN command
488     if (type != TYPE_SCAN) {
489         redis_cmd_append_sstr(&cmdstr, key, key_len);
490     }
491 
492     // Append cursor
493     redis_cmd_append_sstr_long(&cmdstr, it);
494 
495     // Append count if we've got one
496     if (count) {
497         redis_cmd_append_sstr(&cmdstr,"COUNT",sizeof("COUNT")-1);
498         redis_cmd_append_sstr_long(&cmdstr, count);
499     }
500 
501     // Append pattern if we've got one
502     if (pat_len) {
503         redis_cmd_append_sstr(&cmdstr,"MATCH",sizeof("MATCH")-1);
504         redis_cmd_append_sstr(&cmdstr,pat,pat_len);
505     }
506 
507     // Push command to the caller, return length
508     *cmd = cmdstr.c;
509     return cmdstr.len;
510 }
511 
512 /* ZRANGE/ZREVRANGE */
redis_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,int * withscores,short * slot,void ** ctx)513 int redis_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
514                      char *kw, char **cmd, int *cmd_len, int *withscores,
515                      short *slot, void **ctx)
516 {
517     char *key;
518     size_t key_len;
519     zend_long start, end;
520     zend_string *zkey;
521     zval *z_ws = NULL, *z_ele;
522 
523     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sll|z", &key, &key_len,
524                              &start, &end, &z_ws) == FAILURE)
525     {
526         return FAILURE;
527     }
528 
529     // Clear withscores arg
530     *withscores = 0;
531 
532     /* Accept ['withscores' => true], or the legacy `true` value */
533     if (z_ws) {
534         if (Z_TYPE_P(z_ws) == IS_ARRAY) {
535             ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(z_ws), zkey, z_ele) {
536                 ZVAL_DEREF(z_ele);
537                 if (zend_string_equals_literal_ci(zkey, "withscores")) {
538                     *withscores = zval_is_true(z_ele);
539                     break;
540                 }
541             } ZEND_HASH_FOREACH_END();
542         } else if (Z_TYPE_P(z_ws) == IS_TRUE) {
543             *withscores = Z_TYPE_P(z_ws) == IS_TRUE;
544         }
545     }
546 
547     if (*withscores) {
548         *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kdds", key, key_len, start, end,
549             "WITHSCORES", sizeof("WITHSCORES") - 1);
550     } else {
551         *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kdd", key, key_len, start, end);
552     }
553 
554     return SUCCESS;
555 }
556 
redis_zrangebyscore_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,int * withscores,short * slot,void ** ctx)557 int redis_zrangebyscore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
558                             char *kw, char **cmd, int *cmd_len, int *withscores,
559                             short *slot, void **ctx)
560 {
561     char *key, *start, *end;
562     int has_limit = 0;
563     long offset, count;
564     size_t key_len, start_len, end_len;
565     zval *z_opt=NULL, *z_ele;
566     zend_string *zkey;
567     HashTable *ht_opt;
568 
569     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|a", &key, &key_len,
570                              &start, &start_len, &end, &end_len, &z_opt)
571                              ==FAILURE)
572     {
573         return FAILURE;
574     }
575 
576     // Check for an options array
577     if (z_opt && Z_TYPE_P(z_opt) == IS_ARRAY) {
578         ht_opt = Z_ARRVAL_P(z_opt);
579         ZEND_HASH_FOREACH_STR_KEY_VAL(ht_opt, zkey, z_ele) {
580            /* All options require a string key type */
581            if (!zkey) continue;
582            ZVAL_DEREF(z_ele);
583            /* Check for withscores and limit */
584            if (zend_string_equals_literal_ci(zkey, "withscores")) {
585                *withscores = zval_is_true(z_ele);
586            } else if (zend_string_equals_literal_ci(zkey, "limit") && Z_TYPE_P(z_ele) == IS_ARRAY) {
587                 HashTable *htlimit = Z_ARRVAL_P(z_ele);
588                 zval *zoff, *zcnt;
589 
590                 /* We need two arguments (offset and count) */
591                 if ((zoff = zend_hash_index_find(htlimit, 0)) != NULL &&
592                     (zcnt = zend_hash_index_find(htlimit, 1)) != NULL
593                 ) {
594                     /* Set our limit if we can get valid longs from both args */
595                     offset = zval_get_long(zoff);
596                     count = zval_get_long(zcnt);
597                     has_limit = 1;
598                 }
599            }
600         } ZEND_HASH_FOREACH_END();
601     }
602 
603     // Construct our command
604     if (*withscores) {
605         if (has_limit) {
606             *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksssdds", key, key_len,
607                 start, start_len, end, end_len, "LIMIT", 5, offset, count,
608                 "WITHSCORES", 10);
609         } else {
610             *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksss", key, key_len, start,
611                 start_len, end, end_len, "WITHSCORES", 10);
612         }
613     } else {
614         if (has_limit) {
615             *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksssdd", key, key_len, start,
616                 start_len, end, end_len, "LIMIT", 5, offset, count);
617         } else {
618             *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", key, key_len, start,
619                 start_len, end, end_len);
620         }
621     }
622 
623     return SUCCESS;
624 }
625 
626 /* ZUNIONSTORE, ZINTERSTORE */
redis_zinter_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)627 int redis_zinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
628                      char *kw, char **cmd, int *cmd_len, short *slot,
629                      void **ctx)
630 {
631     char *key, *agg_op=NULL;
632     int key_free, argc = 2, keys_count;
633     size_t key_len, agg_op_len = 0;
634     zval *z_keys, *z_weights=NULL, *z_ele;
635     HashTable *ht_keys, *ht_weights=NULL;
636     smart_string cmdstr = {0};
637 
638     // Parse args
639     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa|a!s", &key,
640                              &key_len, &z_keys, &z_weights, &agg_op,
641                              &agg_op_len) == FAILURE)
642     {
643         return FAILURE;
644     }
645 
646     // Grab our keys
647     ht_keys = Z_ARRVAL_P(z_keys);
648 
649     // Nothing to do if there aren't any
650     if ((keys_count = zend_hash_num_elements(ht_keys)) == 0) {
651         return FAILURE;
652     } else {
653         argc += keys_count;
654     }
655 
656     // Handle WEIGHTS
657     if (z_weights != NULL) {
658         ht_weights = Z_ARRVAL_P(z_weights);
659         if (zend_hash_num_elements(ht_weights) != keys_count) {
660             php_error_docref(NULL, E_WARNING,
661                 "WEIGHTS and keys array should be the same size!");
662             return FAILURE;
663         }
664 
665         // "WEIGHTS" + key count
666         argc += keys_count + 1;
667     }
668 
669     // AGGREGATE option
670     if (agg_op_len != 0) {
671         if (strncasecmp(agg_op, "SUM", sizeof("SUM")) &&
672            strncasecmp(agg_op, "MIN", sizeof("MIN")) &&
673            strncasecmp(agg_op, "MAX", sizeof("MAX")))
674         {
675             php_error_docref(NULL, E_WARNING,
676                 "Invalid AGGREGATE option provided!");
677             return FAILURE;
678         }
679 
680         // "AGGREGATE" + type
681         argc += 2;
682     }
683 
684     // Prefix key
685     key_free = redis_key_prefix(redis_sock, &key, &key_len);
686 
687     // Start building our command
688     redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
689     redis_cmd_append_sstr(&cmdstr, key, key_len);
690     redis_cmd_append_sstr_int(&cmdstr, keys_count);
691 
692     // Set our slot, free the key if we prefixed it
693     CMD_SET_SLOT(slot,key,key_len);
694     if (key_free) efree(key);
695 
696     // Process input keys
697     ZEND_HASH_FOREACH_VAL(ht_keys, z_ele) {
698         zend_string *zstr = zval_get_string(z_ele);
699         char *key = ZSTR_VAL(zstr);
700         size_t key_len = ZSTR_LEN(zstr);
701 
702         // Prefix key if necissary
703         int key_free = redis_key_prefix(redis_sock, &key, &key_len);
704 
705         // If we're in Cluster mode, verify the slot is the same
706         if (slot && *slot != cluster_hash_key(key,key_len)) {
707             php_error_docref(NULL, E_WARNING,
708                 "All keys don't hash to the same slot!");
709             efree(cmdstr.c);
710             zend_string_release(zstr);
711             if (key_free) efree(key);
712             return FAILURE;
713         }
714 
715         // Append this input set
716         redis_cmd_append_sstr(&cmdstr, key, key_len);
717 
718         // Cleanup
719         zend_string_release(zstr);
720         if (key_free) efree(key);
721     } ZEND_HASH_FOREACH_END();
722 
723     // Weights
724     if (ht_weights != NULL) {
725         redis_cmd_append_sstr(&cmdstr, "WEIGHTS", sizeof("WEIGHTS")-1);
726 
727         // Process our weights
728         ZEND_HASH_FOREACH_VAL(ht_weights, z_ele) {
729             // Ignore non numeric args unless they're inf/-inf
730             ZVAL_DEREF(z_ele);
731             switch (Z_TYPE_P(z_ele)) {
732                 case IS_LONG:
733                     redis_cmd_append_sstr_long(&cmdstr, Z_LVAL_P(z_ele));
734                     break;
735                 case IS_DOUBLE:
736                     redis_cmd_append_sstr_dbl(&cmdstr, Z_DVAL_P(z_ele));
737                     break;
738                 case IS_STRING: {
739                     double dval;
740                     zend_long lval;
741                     zend_uchar type = is_numeric_string(Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele), &lval, &dval, 0);
742                     if (type == IS_LONG) {
743                         redis_cmd_append_sstr_long(&cmdstr, lval);
744                         break;
745                     } else if (type == IS_DOUBLE) {
746                         redis_cmd_append_sstr_dbl(&cmdstr, dval);
747                         break;
748                     } else if (strncasecmp(Z_STRVAL_P(z_ele), "-inf", sizeof("-inf") - 1) == 0 ||
749                                strncasecmp(Z_STRVAL_P(z_ele), "+inf", sizeof("+inf") - 1) == 0 ||
750                                strncasecmp(Z_STRVAL_P(z_ele), "inf", sizeof("inf") - 1) == 0
751                     ) {
752                         redis_cmd_append_sstr(&cmdstr, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
753                         break;
754                     }
755                     // fall through
756                 }
757                 default:
758                     php_error_docref(NULL, E_WARNING,
759                         "Weights must be numeric or '-inf','inf','+inf'");
760                     efree(cmdstr.c);
761                     return FAILURE;
762             }
763         } ZEND_HASH_FOREACH_END();
764     }
765 
766     // AGGREGATE
767     if (agg_op_len != 0) {
768         redis_cmd_append_sstr(&cmdstr, "AGGREGATE", sizeof("AGGREGATE")-1);
769         redis_cmd_append_sstr(&cmdstr, agg_op, agg_op_len);
770     }
771 
772     // Push out values
773     *cmd     = cmdstr.c;
774     *cmd_len = cmdstr.len;
775 
776     return SUCCESS;
777 }
778 
779 /* SUBSCRIBE/PSUBSCRIBE */
redis_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)780 int redis_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
781                         char *kw, char **cmd, int *cmd_len, short *slot,
782                         void **ctx)
783 {
784     zval *z_arr, *z_chan;
785     HashTable *ht_chan;
786     smart_string cmdstr = {0};
787     subscribeContext *sctx = ecalloc(1, sizeof(*sctx));
788     size_t key_len;
789     int key_free;
790     char *key;
791 
792     if (zend_parse_parameters(ZEND_NUM_ARGS(), "af", &z_arr,
793                              &(sctx->cb), &(sctx->cb_cache)) == FAILURE)
794     {
795         efree(sctx);
796         return FAILURE;
797     }
798 
799     ht_chan    = Z_ARRVAL_P(z_arr);
800     sctx->kw   = kw;
801     sctx->argc = zend_hash_num_elements(ht_chan);
802 
803     if (sctx->argc == 0) {
804         efree(sctx);
805         return FAILURE;
806     }
807 
808     // Start command construction
809     redis_cmd_init_sstr(&cmdstr, sctx->argc, kw, strlen(kw));
810 
811     // Iterate over channels
812     ZEND_HASH_FOREACH_VAL(ht_chan, z_chan) {
813         // We want to deal with strings here
814         zend_string *zstr = zval_get_string(z_chan);
815 
816         // Grab channel name, prefix if required
817         key = ZSTR_VAL(zstr);
818         key_len = ZSTR_LEN(zstr);
819         key_free = redis_key_prefix(redis_sock, &key, &key_len);
820 
821         // Add this channel
822         redis_cmd_append_sstr(&cmdstr, key, key_len);
823 
824         zend_string_release(zstr);
825         // Free our key if it was prefixed
826         if (key_free) efree(key);
827     } ZEND_HASH_FOREACH_END();
828 
829     // Push values out
830     *cmd_len = cmdstr.len;
831     *cmd     = cmdstr.c;
832     *ctx     = (void*)sctx;
833 
834     // Pick a slot at random
835     CMD_RAND_SLOT(slot);
836 
837     return SUCCESS;
838 }
839 
840 /* UNSUBSCRIBE/PUNSUBSCRIBE */
redis_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)841 int redis_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
842                           char *kw, char **cmd, int *cmd_len, short *slot,
843                           void **ctx)
844 {
845     zval *z_arr, *z_chan;
846     HashTable *ht_arr;
847     smart_string cmdstr = {0};
848     subscribeContext *sctx = ecalloc(1, sizeof(*sctx));
849 
850     if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &z_arr) == FAILURE) {
851         efree(sctx);
852         return FAILURE;
853     }
854 
855     ht_arr = Z_ARRVAL_P(z_arr);
856 
857     sctx->argc = zend_hash_num_elements(ht_arr);
858     if (sctx->argc == 0) {
859         efree(sctx);
860         return FAILURE;
861     }
862 
863     redis_cmd_init_sstr(&cmdstr, sctx->argc, kw, strlen(kw));
864 
865     ZEND_HASH_FOREACH_VAL(ht_arr, z_chan) {
866         char *key = Z_STRVAL_P(z_chan);
867         size_t key_len = Z_STRLEN_P(z_chan);
868         int key_free;
869 
870         key_free = redis_key_prefix(redis_sock, &key, &key_len);
871         redis_cmd_append_sstr(&cmdstr, key, key_len);
872         if (key_free) efree(key);
873     } ZEND_HASH_FOREACH_END();
874 
875     // Push out vals
876     *cmd_len = cmdstr.len;
877     *cmd     = cmdstr.c;
878     *ctx     = (void*)sctx;
879 
880     return SUCCESS;
881 }
882 
883 /* ZRANGEBYLEX/ZREVRANGEBYLEX */
redis_zrangebylex_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)884 int redis_zrangebylex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
885                           char *kw, char **cmd, int *cmd_len, short *slot,
886                           void **ctx)
887 {
888     char *key, *min, *max;
889     size_t key_len, min_len, max_len;
890     int argc = ZEND_NUM_ARGS();
891     zend_long offset, count;
892 
893     /* We need either 3 or 5 arguments for this to be valid */
894     if (argc != 3 && argc != 5) {
895         php_error_docref(0, E_WARNING, "Must pass either 3 or 5 arguments");
896         return FAILURE;
897     }
898 
899     if (zend_parse_parameters(argc, "sss|ll", &key, &key_len, &min, &min_len,
900                              &max, &max_len, &offset, &count) == FAILURE)
901     {
902         return FAILURE;
903     }
904 
905     /* min and max must start with '(' or '[', or be either '-' or '+' */
906     if (min_len < 1 || max_len < 1 ||
907        (min[0] != '(' && min[0] != '[' &&
908        (min[0] != '-' || min_len > 1) && (min[0] != '+' || min_len > 1)) ||
909        (max[0] != '(' && max[0] != '[' &&
910        (max[0] != '-' || max_len > 1) && (max[0] != '+' || max_len > 1)))
911     {
912         php_error_docref(0, E_WARNING,
913             "min and max arguments must start with '[' or '('");
914         return FAILURE;
915     }
916 
917     /* Construct command */
918     if (argc == 3) {
919         *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", key, key_len, min, min_len,
920             max, max_len);
921     } else {
922         *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksssll", key, key_len, min, min_len,
923             max, max_len, "LIMIT", 5, offset, count);
924     }
925 
926     return SUCCESS;
927 }
928 
929 /* Validate ZLEX* min/max argument strings */
validate_zlex_arg(const char * arg,size_t len)930 static int validate_zlex_arg(const char *arg, size_t len) {
931     return (len  > 1 && (*arg == '[' || *arg == '(')) ||
932            (len == 1 && (*arg == '+' || *arg == '-'));
933 }
934 
935 /* ZLEXCOUNT/ZREMRANGEBYLEX */
redis_gen_zlex_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)936 int redis_gen_zlex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
937                        char *kw, char **cmd, int *cmd_len, short *slot,
938                        void **ctx)
939 {
940     char *key, *min, *max;
941     size_t key_len, min_len, max_len;
942 
943     /* Parse args */
944     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss", &key, &key_len,
945                              &min, &min_len, &max, &max_len) == FAILURE)
946     {
947         return FAILURE;
948     }
949 
950     /* Quick sanity check on min/max */
951     if (!validate_zlex_arg(min, min_len) || !validate_zlex_arg(max, max_len)) {
952         php_error_docref(NULL, E_WARNING,
953             "Min/Max args can be '-' or '+', or start with '[' or '('");
954         return FAILURE;
955     }
956 
957     /* Construct command */
958     *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", key, key_len, min, min_len,
959         max, max_len);
960 
961     return SUCCESS;
962 }
963 
964 /* EVAL and EVALSHA */
redis_eval_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)965 int redis_eval_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw,
966                    char **cmd, int *cmd_len, short *slot, void **ctx)
967 {
968     char *lua;
969     int argc = 0;
970     zval *z_arr = NULL, *z_ele;
971     HashTable *ht_arr;
972     zend_long num_keys = 0;
973     smart_string cmdstr = {0};
974     size_t lua_len;
975     zend_string *zstr;
976     short prevslot = -1;
977 
978     /* Parse args */
979     if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|al", &lua, &lua_len,
980                              &z_arr, &num_keys) == FAILURE)
981     {
982         return FAILURE;
983     }
984 
985     /* Grab arg count */
986     if (z_arr != NULL) {
987         ht_arr = Z_ARRVAL_P(z_arr);
988         argc = zend_hash_num_elements(ht_arr);
989     }
990 
991     /* EVAL[SHA] {script || sha1} {num keys}  */
992     redis_cmd_init_sstr(&cmdstr, 2 + argc, kw, strlen(kw));
993     redis_cmd_append_sstr(&cmdstr, lua, lua_len);
994     redis_cmd_append_sstr_long(&cmdstr, num_keys);
995 
996     // Iterate over our args if we have any
997     if (argc > 0) {
998         ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
999             zstr = zval_get_string(z_ele);
1000 
1001             /* If we're still on a key, prefix it check slot */
1002             if (num_keys-- > 0) {
1003                 redis_cmd_append_sstr_key(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr), redis_sock, slot);
1004 
1005                 /* If we have been passed a slot, all keys must match */
1006                 if (slot) {
1007                     if (prevslot != -1 && prevslot != *slot) {
1008                         zend_string_release(zstr);
1009                         php_error_docref(0, E_WARNING, "All keys do not map to the same slot");
1010                         return FAILURE;
1011                     }
1012                     prevslot = *slot;
1013                 }
1014             } else {
1015                 redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
1016             }
1017 
1018             zend_string_release(zstr);
1019         } ZEND_HASH_FOREACH_END();
1020     } else {
1021         /* Any slot will do */
1022         CMD_RAND_SLOT(slot);
1023     }
1024 
1025     *cmd = cmdstr.c;
1026     *cmd_len = cmdstr.len;
1027     return SUCCESS;
1028 }
1029 
1030 /* Commands that take a key followed by a variable list of serializable
1031  * values (RPUSH, LPUSH, SADD, SREM, etc...) */
redis_key_varval_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)1032 int redis_key_varval_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1033                          char *kw, char **cmd, int *cmd_len, short *slot,
1034                          void **ctx)
1035 {
1036     zval *z_args;
1037     smart_string cmdstr = {0};
1038     size_t i;
1039     int argc = ZEND_NUM_ARGS();
1040 
1041     // We at least need a key and one value
1042     if (argc < 2) {
1043         return FAILURE;
1044     }
1045 
1046     // Make sure we at least have a key, and we can get other args
1047     z_args = emalloc(argc * sizeof(zval));
1048     if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
1049         efree(z_args);
1050         return FAILURE;
1051     }
1052 
1053     /* Initialize our command */
1054     redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
1055 
1056     /* Append key */
1057     zend_string *zstr = zval_get_string(&z_args[0]);
1058     redis_cmd_append_sstr_key(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr), redis_sock, slot);
1059     zend_string_release(zstr);
1060 
1061     /* Add members */
1062     for (i = 1; i < argc; i++ ){
1063         redis_cmd_append_sstr_zval(&cmdstr, &z_args[i], redis_sock);
1064     }
1065 
1066     // Push out values
1067     *cmd     = cmdstr.c;
1068     *cmd_len = cmdstr.len;
1069 
1070     // Cleanup arg array
1071     efree(z_args);
1072 
1073     // Success!
1074     return SUCCESS;
1075 }
1076 
1077 /* Commands that take a key and then an array of values */
1078 #define VAL_TYPE_VALUES 1
1079 #define VAL_TYPE_STRINGS 2
gen_key_arr_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,int valtype,char ** cmd,int * cmd_len,short * slot,void ** ctx)1080 static int gen_key_arr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1081                            char *kw, int valtype, char **cmd, int *cmd_len,
1082                            short *slot, void **ctx)
1083 {
1084     zval *z_arr, *z_val;
1085     HashTable *ht_arr;
1086     smart_string cmdstr = {0};
1087     zend_string *zstr;
1088     int key_free, val_free, argc = 1;
1089     size_t val_len, key_len;
1090     char *key, *val;
1091 
1092     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len,
1093                               &z_arr) == FAILURE ||
1094                               zend_hash_num_elements(Z_ARRVAL_P(z_arr)) == 0)
1095     {
1096         return FAILURE;
1097     }
1098 
1099     /* Start constructing our command */
1100     ht_arr = Z_ARRVAL_P(z_arr);
1101     argc += zend_hash_num_elements(ht_arr);
1102     redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
1103 
1104     /* Prefix if required and append the key name */
1105     key_free = redis_key_prefix(redis_sock, &key, &key_len);
1106     redis_cmd_append_sstr(&cmdstr, key, key_len);
1107     CMD_SET_SLOT(slot, key, key_len);
1108     if (key_free) efree(key);
1109 
1110     /* Iterate our hash table, serializing and appending values */
1111     assert(valtype == VAL_TYPE_VALUES || valtype == VAL_TYPE_STRINGS);
1112     ZEND_HASH_FOREACH_VAL(ht_arr, z_val) {
1113         if (valtype == VAL_TYPE_VALUES) {
1114             val_free = redis_pack(redis_sock, z_val, &val, &val_len);
1115             redis_cmd_append_sstr(&cmdstr, val, val_len);
1116             if (val_free) efree(val);
1117         } else {
1118             zstr = zval_get_string(z_val);
1119             redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
1120             zend_string_release(zstr);
1121         }
1122     } ZEND_HASH_FOREACH_END();
1123 
1124     *cmd_len = cmdstr.len;
1125     *cmd = cmdstr.c;
1126 
1127     return SUCCESS;
1128 }
1129 
redis_key_val_arr_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)1130 int redis_key_val_arr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1131                         char *kw, char **cmd, int *cmd_len, short *slot,
1132                         void **ctx)
1133 {
1134     return gen_key_arr_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, kw,
1135         VAL_TYPE_VALUES, cmd, cmd_len, slot, ctx);
1136 }
1137 
redis_key_str_arr_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)1138 int redis_key_str_arr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1139                         char *kw, char **cmd, int *cmd_len, short *slot,
1140                         void **ctx)
1141 {
1142     return gen_key_arr_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, kw,
1143         VAL_TYPE_STRINGS, cmd, cmd_len, slot, ctx);
1144 }
1145 
1146 /* Generic function that takes a variable number of keys, with an optional
1147  * timeout value.  This can handle various SUNION/SUNIONSTORE/BRPOP type
1148  * commands. */
gen_varkey_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,int kw_len,int min_argc,int has_timeout,char ** cmd,int * cmd_len,short * slot)1149 static int gen_varkey_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1150                           char *kw, int kw_len, int min_argc, int has_timeout,
1151                           char **cmd, int *cmd_len, short *slot)
1152 {
1153     zval *z_args, *z_ele;
1154     HashTable *ht_arr;
1155     char *key;
1156     int key_free, i, tail;
1157     size_t key_len;
1158     int single_array = 0, argc = ZEND_NUM_ARGS();
1159     smart_string cmdstr = {0};
1160     long timeout = 0;
1161     short kslot = -1;
1162     zend_string *zstr;
1163 
1164     if (argc < min_argc) {
1165         zend_wrong_param_count();
1166         return FAILURE;
1167     }
1168 
1169     // Allocate args
1170     z_args = emalloc(argc * sizeof(zval));
1171     if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
1172         efree(z_args);
1173         return FAILURE;
1174     }
1175 
1176     // Handle our "single array" case
1177     if (has_timeout == 0) {
1178         single_array = argc==1 && Z_TYPE(z_args[0]) == IS_ARRAY;
1179     } else {
1180         single_array = argc==2 && Z_TYPE(z_args[0]) == IS_ARRAY &&
1181             Z_TYPE(z_args[1]) == IS_LONG;
1182         timeout = Z_LVAL(z_args[1]);
1183     }
1184 
1185     // If we're running a single array, rework args
1186     if (single_array) {
1187         ht_arr = Z_ARRVAL(z_args[0]);
1188         argc = zend_hash_num_elements(ht_arr);
1189         if (has_timeout) argc++;
1190         efree(z_args);
1191         z_args = NULL;
1192 
1193         /* If the array is empty, we can simply abort */
1194         if (argc == 0) return FAILURE;
1195     }
1196 
1197     // Begin construction of our command
1198     redis_cmd_init_sstr(&cmdstr, argc, kw, kw_len);
1199 
1200     if (single_array) {
1201         ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
1202             zstr = zval_get_string(z_ele);
1203             key = ZSTR_VAL(zstr);
1204             key_len = ZSTR_LEN(zstr);
1205             key_free = redis_key_prefix(redis_sock, &key, &key_len);
1206 
1207             // Protect against CROSSLOT errors
1208             if (slot) {
1209                 if (kslot == -1) {
1210                     kslot = cluster_hash_key(key, key_len);
1211                 } else if (cluster_hash_key(key,key_len)!=kslot) {
1212                     zend_string_release(zstr);
1213                     if (key_free) efree(key);
1214                     php_error_docref(NULL, E_WARNING,
1215                         "Not all keys hash to the same slot!");
1216                     return FAILURE;
1217                 }
1218             }
1219 
1220             // Append this key, free it if we prefixed
1221             redis_cmd_append_sstr(&cmdstr, key, key_len);
1222             zend_string_release(zstr);
1223             if (key_free) efree(key);
1224         } ZEND_HASH_FOREACH_END();
1225         if (has_timeout) {
1226             redis_cmd_append_sstr_long(&cmdstr, timeout);
1227         }
1228     } else {
1229         if (has_timeout && Z_TYPE(z_args[argc-1])!=IS_LONG) {
1230             php_error_docref(NULL, E_ERROR,
1231                 "Timeout value must be a LONG");
1232             efree(z_args);
1233             return FAILURE;
1234         }
1235 
1236         tail = has_timeout ? argc-1 : argc;
1237         for(i = 0; i < tail; i++) {
1238             zstr = zval_get_string(&z_args[i]);
1239             key = ZSTR_VAL(zstr);
1240             key_len = ZSTR_LEN(zstr);
1241 
1242             key_free = redis_key_prefix(redis_sock, &key, &key_len);
1243 
1244             /* Protect against CROSSSLOT errors if we've got a slot */
1245             if (slot) {
1246                 if ( kslot == -1) {
1247                     kslot = cluster_hash_key(key, key_len);
1248                 } else if (cluster_hash_key(key,key_len)!=kslot) {
1249                     php_error_docref(NULL, E_WARNING,
1250                         "Not all keys hash to the same slot");
1251                     zend_string_release(zstr);
1252                     if (key_free) efree(key);
1253                     efree(z_args);
1254                     return FAILURE;
1255                 }
1256             }
1257 
1258             // Append this key
1259             redis_cmd_append_sstr(&cmdstr, key, key_len);
1260             zend_string_release(zstr);
1261             if (key_free) efree(key);
1262         }
1263         if (has_timeout) {
1264             redis_cmd_append_sstr_long(&cmdstr, Z_LVAL(z_args[tail]));
1265         }
1266 
1267         // Cleanup args
1268         efree(z_args);
1269     }
1270 
1271     // Push out parameters
1272     if (slot) *slot = kslot;
1273     *cmd = cmdstr.c;
1274     *cmd_len = cmdstr.len;
1275 
1276     return SUCCESS;
1277 }
1278 
1279 /* Generic handling of every blocking pop command (BLPOP, BZPOP[MIN/MAX], etc */
redis_blocking_pop_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)1280 int redis_blocking_pop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1281                            char *kw, char **cmd, int *cmd_len, short *slot,
1282                            void **ctx)
1283 {
1284     return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, kw,
1285         strlen(kw), 2, 2, cmd, cmd_len, slot);
1286 }
1287 
1288 /*
1289  * Commands with specific signatures or that need unique functions because they
1290  * have specific processing (argument validation, etc) that make them unique
1291  */
1292 
1293 /* Attempt to pull a long expiry from a zval.  We're more restrictave than zval_get_long
1294  * because that function will return integers from things like open file descriptors
1295  * which should simply fail as a TTL */
redis_try_get_expiry(zval * zv,zend_long * lval)1296 static int redis_try_get_expiry(zval *zv, zend_long *lval) {
1297     double dval;
1298 
1299     /* Success on an actual long or double */
1300     if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) {
1301         *lval = zval_get_long(zv);
1302         return SUCCESS;
1303     }
1304 
1305     /* Automatically fail if we're not a string */
1306     if (Z_TYPE_P(zv) != IS_STRING)
1307         return FAILURE;
1308 
1309     /* Attempt to get a long from the string */
1310     switch (is_numeric_string(Z_STRVAL_P(zv), Z_STRLEN_P(zv), lval, &dval, 0)) {
1311         case IS_DOUBLE:
1312             *lval = dval;
1313             /* fallthrough */
1314         case IS_LONG:
1315             return SUCCESS;
1316         default:
1317             return FAILURE;
1318     }
1319 }
1320 
1321 /* SET */
redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1322 int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1323                   char **cmd, int *cmd_len, short *slot, void **ctx)
1324 {
1325     smart_string cmdstr = {0};
1326     zval *z_value, *z_opts=NULL;
1327     char *key = NULL, *exp_type = NULL, *set_type = NULL;
1328     long exp_set = 0, keep_ttl = 0;
1329     zend_long expire = -1;
1330     size_t key_len;
1331 
1332     // Make sure the function is being called correctly
1333     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz|z", &key, &key_len,
1334                              &z_value, &z_opts) == FAILURE)
1335     {
1336         return FAILURE;
1337     }
1338 
1339     // Check for an options array
1340     if (z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) {
1341         HashTable *kt = Z_ARRVAL_P(z_opts);
1342         zend_string *zkey;
1343         zval *v;
1344 
1345         /* Iterate our option array */
1346         ZEND_HASH_FOREACH_STR_KEY_VAL(kt, zkey, v) {
1347             ZVAL_DEREF(v);
1348             /* Detect PX or EX argument and validate timeout */
1349             if (zkey && ZSTR_IS_EX_PX_ARG(zkey)) {
1350                 exp_set = 1;
1351 
1352                 /* Set expire type */
1353                 exp_type = ZSTR_VAL(zkey);
1354 
1355                 /* Try to extract timeout */
1356                 if (Z_TYPE_P(v) == IS_LONG) {
1357                     expire = Z_LVAL_P(v);
1358                 } else if (Z_TYPE_P(v) == IS_STRING) {
1359                     expire = atol(Z_STRVAL_P(v));
1360                 }
1361             } else if (Z_TYPE_P(v) == IS_STRING) {
1362                 if (ZVAL_STRICMP_STATIC(v, "KEEPTTL")) {
1363                     keep_ttl  = 1;
1364                 } else if (ZVAL_IS_NX_XX_ARG(v)) {
1365                     set_type = Z_STRVAL_P(v);
1366                 }
1367             }
1368         } ZEND_HASH_FOREACH_END();
1369     } else if (z_opts && Z_TYPE_P(z_opts) != IS_NULL) {
1370         if (redis_try_get_expiry(z_opts, &expire) == FAILURE) {
1371             php_error_docref(NULL, E_WARNING, "Expire must be a long, double, or a numeric string");
1372             return FAILURE;
1373         }
1374 
1375         exp_set = 1;
1376     }
1377 
1378     /* Protect the user from syntax errors but give them some info about what's wrong */
1379     if (exp_set && expire < 1) {
1380         php_error_docref(NULL, E_WARNING, "EXPIRE can't be < 1");
1381         return FAILURE;
1382     } else if (exp_type && keep_ttl) {
1383         php_error_docref(NULL, E_WARNING, "KEEPTTL can't be combined with EX or PX option");
1384         return FAILURE;
1385     }
1386 
1387     /* Backward compatibility:  If we are passed no options except an EXPIRE ttl, we
1388      * actually execute a SETEX command */
1389     if (expire > 0 && !exp_type && !set_type && !keep_ttl) {
1390         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SETEX", "klv", key, key_len, expire, z_value);
1391         return SUCCESS;
1392     }
1393 
1394     /* Calculate argc based on options set */
1395     int argc = 2 + (exp_type ? 2 : 0) + (set_type != NULL) + (keep_ttl != 0);
1396 
1397     /* Initial SET <key> <value> */
1398     redis_cmd_init_sstr(&cmdstr, argc, "SET", 3);
1399     redis_cmd_append_sstr_key(&cmdstr, key, key_len, redis_sock, slot);
1400     redis_cmd_append_sstr_zval(&cmdstr, z_value, redis_sock);
1401 
1402     if (exp_type) {
1403         redis_cmd_append_sstr(&cmdstr, exp_type, strlen(exp_type));
1404         redis_cmd_append_sstr_long(&cmdstr, (long)expire);
1405     }
1406 
1407     if (set_type)
1408         redis_cmd_append_sstr(&cmdstr, set_type, strlen(set_type));
1409     if (keep_ttl)
1410         redis_cmd_append_sstr(&cmdstr, "KEEPTTL", 7);
1411 
1412     /* Push command and length to the caller */
1413     *cmd = cmdstr.c;
1414     *cmd_len = cmdstr.len;
1415 
1416     return SUCCESS;
1417 }
1418 
1419 /* BRPOPLPUSH */
redis_brpoplpush_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1420 int redis_brpoplpush_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1421                          char **cmd, int *cmd_len, short *slot, void **ctx)
1422 {
1423     char *key1, *key2;
1424     size_t key1_len, key2_len;
1425     int key1_free, key2_free;
1426     short slot1, slot2;
1427     zend_long timeout;
1428 
1429     if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssl", &key1, &key1_len,
1430                              &key2, &key2_len, &timeout) == FAILURE)
1431     {
1432         return FAILURE;
1433     }
1434 
1435     // Key prefixing
1436     key1_free = redis_key_prefix(redis_sock, &key1, &key1_len);
1437     key2_free = redis_key_prefix(redis_sock, &key2, &key2_len);
1438 
1439     // In cluster mode, verify the slots match
1440     if (slot) {
1441         slot1 = cluster_hash_key(key1, key1_len);
1442         slot2 = cluster_hash_key(key2, key2_len);
1443         if (slot1 != slot2) {
1444             php_error_docref(NULL, E_WARNING,
1445                "Keys hash to different slots!");
1446             if (key1_free) efree(key1);
1447             if (key2_free) efree(key2);
1448             return FAILURE;
1449         }
1450 
1451         // Both slots are the same
1452         *slot = slot1;
1453     }
1454 
1455     // Consistency with Redis, if timeout < 0 use RPOPLPUSH
1456     if (timeout < 0) {
1457         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "RPOPLPUSH", "ss", key1, key1_len,
1458                                      key2, key2_len);
1459     } else {
1460         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "BRPOPLPUSH", "ssd", key1, key1_len,
1461                                      key2, key2_len, timeout);
1462     }
1463 
1464     if (key1_free) efree(key1);
1465     if (key2_free) efree(key2);
1466     return SUCCESS;
1467 }
1468 
1469 /* To maintain backward compatibility with earlier versions of phpredis, we
1470  * allow for an optional "increment by" argument for INCR and DECR even though
1471  * that's not how Redis proper works */
1472 #define TYPE_INCR 0
1473 #define TYPE_DECR 1
1474 
1475 /* Handle INCR(BY) and DECR(BY) depending on optional increment value */
1476 static int
redis_atomic_increment(INTERNAL_FUNCTION_PARAMETERS,int type,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1477 redis_atomic_increment(INTERNAL_FUNCTION_PARAMETERS, int type,
1478                        RedisSock *redis_sock, char **cmd, int *cmd_len,
1479                        short *slot, void **ctx)
1480 {
1481     char *key;
1482     size_t key_len;
1483     zend_long val = 1;
1484 
1485     if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &key, &key_len,
1486                               &val) == FAILURE)
1487     {
1488         return FAILURE;
1489     }
1490 
1491     /* If our value is 1 we use INCR/DECR.  For other values, treat the call as
1492      * an INCRBY or DECRBY call */
1493     if (type == TYPE_INCR) {
1494         if (val == 1) {
1495             *cmd_len = REDIS_CMD_SPPRINTF(cmd, "INCR", "k", key, key_len);
1496         } else {
1497             *cmd_len = REDIS_CMD_SPPRINTF(cmd, "INCRBY", "kl", key, key_len, val);
1498         }
1499     } else {
1500         if (val == 1) {
1501             *cmd_len = REDIS_CMD_SPPRINTF(cmd, "DECR", "k", key, key_len);
1502         } else {
1503             *cmd_len = REDIS_CMD_SPPRINTF(cmd, "DECRBY", "kl", key, key_len, val);
1504         }
1505     }
1506 
1507     /* Success */
1508     return SUCCESS;
1509 }
1510 
1511 /* INCR */
redis_incr_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1512 int redis_incr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1513                    char **cmd, int *cmd_len, short *slot, void **ctx)
1514 {
1515     return redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU,
1516         TYPE_INCR, redis_sock, cmd, cmd_len, slot, ctx);
1517 }
1518 
1519 /* DECR */
redis_decr_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1520 int redis_decr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1521                    char **cmd, int *cmd_len, short *slot, void **ctx)
1522 {
1523     return redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU,
1524         TYPE_DECR, redis_sock, cmd, cmd_len, slot, ctx);
1525 }
1526 
1527 /* HINCRBY */
redis_hincrby_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1528 int redis_hincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1529                       char **cmd, int *cmd_len, short *slot, void **ctx)
1530 {
1531     char *key, *mem;
1532     size_t key_len, mem_len;
1533     zend_long byval;
1534 
1535     if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssl", &key, &key_len,
1536                              &mem, &mem_len, &byval) == FAILURE)
1537     {
1538         return FAILURE;
1539     }
1540 
1541     // Construct command
1542     *cmd_len = REDIS_CMD_SPPRINTF(cmd, "HINCRBY", "ksl", key, key_len, mem, mem_len, byval);
1543 
1544     // Success
1545     return SUCCESS;
1546 }
1547 
1548 /* HINCRBYFLOAT */
redis_hincrbyfloat_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1549 int redis_hincrbyfloat_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1550                            char **cmd, int *cmd_len, short *slot, void **ctx)
1551 {
1552     char *key, *mem;
1553     size_t key_len, mem_len;
1554     double byval;
1555 
1556     if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssd", &key, &key_len,
1557                              &mem, &mem_len, &byval) == FAILURE)
1558     {
1559         return FAILURE;
1560     }
1561 
1562     // Construct command
1563     *cmd_len = REDIS_CMD_SPPRINTF(cmd, "HINCRBYFLOAT", "ksf", key, key_len, mem,
1564                                  mem_len, byval);
1565 
1566     // Success
1567     return SUCCESS;
1568 }
1569 
1570 /* HMGET */
redis_hmget_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1571 int redis_hmget_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1572                     char **cmd, int *cmd_len, short *slot, void **ctx)
1573 {
1574     char *key;
1575     zval *z_arr, *z_mems, *z_mem;
1576     int i, count, valid = 0, key_free;
1577     size_t key_len;
1578     HashTable *ht_arr;
1579     smart_string cmdstr = {0};
1580 
1581     // Parse arguments
1582     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len,
1583                              &z_arr) == FAILURE)
1584     {
1585         return FAILURE;
1586     }
1587 
1588     // Our HashTable
1589     ht_arr = Z_ARRVAL_P(z_arr);
1590 
1591     // We can abort if we have no elements
1592     if ((count = zend_hash_num_elements(ht_arr)) == 0) {
1593         return FAILURE;
1594     }
1595 
1596     // Allocate memory for mems+1 so we can have a sentinel
1597     z_mems = ecalloc(count + 1, sizeof(zval));
1598 
1599     // Iterate over our member array
1600     ZEND_HASH_FOREACH_VAL(ht_arr, z_mem) {
1601         ZVAL_DEREF(z_mem);
1602         // We can only handle string or long values here
1603         if ((Z_TYPE_P(z_mem) == IS_STRING && Z_STRLEN_P(z_mem) > 0)
1604             || Z_TYPE_P(z_mem) == IS_LONG
1605         ) {
1606             // Copy into our member array
1607             ZVAL_ZVAL(&z_mems[valid], z_mem, 1, 0);
1608 
1609             // Increment the member count to actually send
1610             valid++;
1611         }
1612     } ZEND_HASH_FOREACH_END();
1613 
1614     // If nothing was valid, fail
1615     if (valid == 0) {
1616         efree(z_mems);
1617         return FAILURE;
1618     }
1619 
1620     // Sentinel so we can free this even if it's used and then we discard
1621     // the transaction manually or there is a transaction failure
1622     ZVAL_NULL(&z_mems[valid]);
1623 
1624     // Start command construction
1625     redis_cmd_init_sstr(&cmdstr, valid+1, "HMGET", sizeof("HMGET")-1);
1626 
1627     // Prefix our key
1628     key_free = redis_key_prefix(redis_sock, &key, &key_len);
1629 
1630     redis_cmd_append_sstr(&cmdstr, key, key_len);
1631 
1632     // Iterate over members, appending as arguments
1633     for(i = 0; i< valid; i++) {
1634         zend_string *zstr = zval_get_string(&z_mems[i]);
1635         redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
1636         zend_string_release(zstr);
1637     }
1638 
1639     // Set our slot
1640     CMD_SET_SLOT(slot,key,key_len);
1641 
1642     // Free our key if we prefixed it
1643     if (key_free) efree(key);
1644 
1645     // Push out command, length, and key context
1646     *cmd     = cmdstr.c;
1647     *cmd_len = cmdstr.len;
1648     *ctx     = (void*)z_mems;
1649 
1650     // Success!
1651     return SUCCESS;
1652 }
1653 
1654 /* HMSET */
redis_hmset_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1655 int redis_hmset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1656                     char **cmd, int *cmd_len, short *slot, void **ctx)
1657 {
1658     char *key;
1659     int key_free, count;
1660     size_t key_len;
1661     zend_ulong idx;
1662     zval *z_arr;
1663     HashTable *ht_vals;
1664     smart_string cmdstr = {0};
1665     zend_string *zkey;
1666     zval *z_val;
1667 
1668     // Parse args
1669     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len,
1670                              &z_arr) == FAILURE)
1671     {
1672         return FAILURE;
1673     }
1674 
1675     // We can abort if we have no fields
1676     if ((count = zend_hash_num_elements(Z_ARRVAL_P(z_arr))) == 0) {
1677         return FAILURE;
1678     }
1679 
1680     // Prefix our key
1681     key_free = redis_key_prefix(redis_sock, &key, &key_len);
1682 
1683     // Grab our array as a HashTable
1684     ht_vals = Z_ARRVAL_P(z_arr);
1685 
1686     // Initialize our HMSET command (key + 2x each array entry), add key
1687     redis_cmd_init_sstr(&cmdstr, 1+(count*2), "HMSET", sizeof("HMSET")-1);
1688     redis_cmd_append_sstr(&cmdstr, key, key_len);
1689 
1690     // Start traversing our key => value array
1691     ZEND_HASH_FOREACH_KEY_VAL(ht_vals, idx, zkey, z_val) {
1692         char *mem, *val, kbuf[40];
1693         size_t val_len;
1694         int val_free;
1695         unsigned int mem_len;
1696 
1697         // If the hash key is an integer, convert it to a string
1698         if (zkey) {
1699             mem_len = ZSTR_LEN(zkey);
1700             mem = ZSTR_VAL(zkey);
1701         } else {
1702             mem_len = snprintf(kbuf, sizeof(kbuf), ZEND_LONG_FMT, idx);
1703             mem = (char*)kbuf;
1704         }
1705 
1706         // Serialize value (if directed)
1707         val_free = redis_pack(redis_sock, z_val, &val, &val_len);
1708 
1709         // Append the key and value to our command
1710         redis_cmd_append_sstr(&cmdstr, mem, mem_len);
1711         redis_cmd_append_sstr(&cmdstr, val, val_len);
1712 
1713         // Free our value if we serialized it
1714         if (val_free) efree(val);
1715     } ZEND_HASH_FOREACH_END();
1716 
1717     // Set slot if directed
1718     CMD_SET_SLOT(slot,key,key_len);
1719 
1720     // Free our key if we prefixed it
1721     if (key_free) efree(key);
1722 
1723     // Push return pointers
1724     *cmd_len = cmdstr.len;
1725     *cmd = cmdstr.c;
1726 
1727     // Success!
1728     return SUCCESS;
1729 }
1730 
1731 /* HSTRLEN */
1732 int
redis_hstrlen_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1733 redis_hstrlen_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1734                   char **cmd, int *cmd_len, short *slot, void **ctx)
1735 {
1736     char *key, *field;
1737     size_t key_len, field_len;
1738 
1739     if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &key, &key_len,
1740                               &field, &field_len) == FAILURE
1741     ) {
1742         return FAILURE;
1743     }
1744 
1745     *cmd_len = REDIS_CMD_SPPRINTF(cmd, "HSTRLEN", "ks", key, key_len, field, field_len);
1746 
1747     return SUCCESS;
1748 }
1749 
1750 /* BITPOS */
redis_bitpos_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1751 int redis_bitpos_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1752                      char **cmd, int *cmd_len, short *slot, void **ctx)
1753 {
1754     char *key;
1755     int argc;
1756     zend_long bit, start, end;
1757     size_t key_len;
1758 
1759     argc = ZEND_NUM_ARGS();
1760     if (zend_parse_parameters(argc, "sl|ll", &key, &key_len, &bit,
1761                              &start, &end) == FAILURE)
1762     {
1763         return FAILURE;
1764     }
1765 
1766     // Prevalidate bit
1767     if (bit != 0 && bit != 1) {
1768         return FAILURE;
1769     }
1770 
1771     // Construct command based on arg count
1772     if (argc == 2) {
1773         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITPOS", "kd", key, key_len, bit);
1774     } else if (argc == 3) {
1775         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITPOS", "kdd", key, key_len, bit,
1776                                      start);
1777     } else {
1778         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITPOS", "kddd", key, key_len, bit,
1779                                      start, end);
1780     }
1781 
1782     return SUCCESS;
1783 }
1784 
1785 /* BITOP */
redis_bitop_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1786 int redis_bitop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1787                     char **cmd, int *cmd_len, short *slot, void **ctx)
1788 {
1789     zval *z_args;
1790     char *key;
1791     size_t key_len;
1792     int i, key_free, argc = ZEND_NUM_ARGS();
1793     smart_string cmdstr = {0};
1794     short kslot;
1795     zend_string *zstr;
1796 
1797     // Allocate space for args, parse them as an array
1798     z_args = emalloc(argc * sizeof(zval));
1799     if (zend_get_parameters_array(ht, argc, z_args) == FAILURE ||
1800        argc < 3 || Z_TYPE(z_args[0]) != IS_STRING)
1801     {
1802         efree(z_args);
1803         return FAILURE;
1804     }
1805 
1806     // If we were passed a slot pointer, init to a sentinel value
1807     if (slot) *slot = -1;
1808 
1809     // Initialize command construction, add our operation argument
1810     redis_cmd_init_sstr(&cmdstr, argc, "BITOP", sizeof("BITOP")-1);
1811     redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0]));
1812 
1813     // Now iterate over our keys argument
1814     for (i = 1; i < argc; i++) {
1815         // Make sure we've got a string
1816         zstr = zval_get_string(&z_args[i]);
1817 
1818         // Grab this key and length
1819         key = ZSTR_VAL(zstr);
1820         key_len = ZSTR_LEN(zstr);
1821 
1822         // Prefix key, append
1823         key_free = redis_key_prefix(redis_sock, &key, &key_len);
1824         redis_cmd_append_sstr(&cmdstr, key, key_len);
1825 
1826         // Verify slot if this is a Cluster request
1827         if (slot) {
1828             kslot = cluster_hash_key(key, key_len);
1829             if (*slot == -1 || kslot != *slot) {
1830                 php_error_docref(NULL, E_WARNING,
1831                     "Warning, not all keys hash to the same slot!");
1832                 zend_string_release(zstr);
1833                 if (key_free) efree(key);
1834                 efree(z_args);
1835                 return FAILURE;
1836             }
1837             *slot = kslot;
1838         }
1839 
1840         zend_string_release(zstr);
1841         if (key_free) efree(key);
1842     }
1843 
1844     // Free our argument array
1845     efree(z_args);
1846 
1847     // Push out variables
1848     *cmd = cmdstr.c;
1849     *cmd_len = cmdstr.len;
1850 
1851     return SUCCESS;
1852 }
1853 
1854 /* BITCOUNT */
redis_bitcount_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1855 int redis_bitcount_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1856                      char **cmd, int *cmd_len, short *slot, void **ctx)
1857 {
1858     char *key;
1859     size_t key_len;
1860     zend_long start = 0, end = -1;
1861 
1862     if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|ll", &key, &key_len,
1863                              &start, &end) == FAILURE)
1864     {
1865         return FAILURE;
1866     }
1867 
1868     *cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITCOUNT", "kdd", key, key_len,
1869                                  (int)start, (int)end);
1870 
1871     return SUCCESS;
1872 }
1873 
1874 /* PFADD and PFMERGE are the same except that in one case we serialize,
1875  * and in the other case we key prefix */
redis_gen_pf_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,int kw_len,int is_keys,char ** cmd,int * cmd_len,short * slot)1876 static int redis_gen_pf_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1877                             char *kw, int kw_len, int is_keys, char **cmd,
1878                             int *cmd_len, short *slot)
1879 {
1880     zval *z_arr, *z_ele;
1881     HashTable *ht_arr;
1882     smart_string cmdstr = {0};
1883     char *mem, *key;
1884     int key_free, mem_free, argc=1;
1885     size_t key_len, mem_len;
1886     zend_string *zstr;
1887 
1888     // Parse arguments
1889     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len,
1890                              &z_arr) == FAILURE)
1891     {
1892         return FAILURE;
1893     }
1894 
1895     // Grab HashTable, count total argc
1896     ht_arr = Z_ARRVAL_P(z_arr);
1897     argc += zend_hash_num_elements(ht_arr);
1898 
1899     // We need at least two arguments
1900     if (argc < 2) {
1901         return FAILURE;
1902     }
1903 
1904     // Prefix key, set initial hash slot
1905     key_free = redis_key_prefix(redis_sock, &key, &key_len);
1906     if (slot) *slot = cluster_hash_key(key, key_len);
1907 
1908     // Start command construction
1909     redis_cmd_init_sstr(&cmdstr, argc, kw, kw_len);
1910     redis_cmd_append_sstr(&cmdstr, key, key_len);
1911 
1912     // Free key if we prefixed
1913     if (key_free) efree(key);
1914 
1915     // Now iterate over the rest of our keys or values
1916     ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
1917         // Prefix keys, serialize values
1918         if (is_keys) {
1919             zstr = zval_get_string(z_ele);
1920             mem = ZSTR_VAL(zstr);
1921             mem_len = ZSTR_LEN(zstr);
1922 
1923             // Key prefix
1924             mem_free = redis_key_prefix(redis_sock, &mem, &mem_len);
1925 
1926             // Verify slot
1927             if (slot && *slot != cluster_hash_key(mem, mem_len)) {
1928                 php_error_docref(0, E_WARNING,
1929                     "All keys must hash to the same slot!");
1930                 zend_string_release(zstr);
1931                 if (key_free) efree(key);
1932                 return FAILURE;
1933             }
1934         } else {
1935             mem_free = redis_pack(redis_sock, z_ele, &mem, &mem_len);
1936 
1937             zstr = NULL;
1938             if (!mem_free) {
1939                 zstr = zval_get_string(z_ele);
1940                 mem = ZSTR_VAL(zstr);
1941                 mem_len = ZSTR_LEN(zstr);
1942             }
1943         }
1944 
1945         // Append our key or member
1946         redis_cmd_append_sstr(&cmdstr, mem, mem_len);
1947 
1948         // Clean up any allocated memory
1949         if (zstr) zend_string_release(zstr);
1950         if (mem_free) efree(mem);
1951     } ZEND_HASH_FOREACH_END();
1952 
1953     // Push output arguments
1954     *cmd = cmdstr.c;
1955     *cmd_len = cmdstr.len;
1956 
1957     return SUCCESS;
1958 }
1959 
1960 /* PFADD */
redis_pfadd_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1961 int redis_pfadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1962                     char **cmd, int *cmd_len, short *slot, void **ctx)
1963 {
1964     return redis_gen_pf_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
1965         "PFADD", sizeof("PFADD")-1, 0, cmd, cmd_len, slot);
1966 }
1967 
1968 /* PFMERGE */
redis_pfmerge_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1969 int redis_pfmerge_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1970                       char **cmd, int *cmd_len, short *slot, void **ctx)
1971 {
1972     return redis_gen_pf_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
1973         "PFMERGE", sizeof("PFMERGE")-1, 1, cmd, cmd_len, slot);
1974 }
1975 
1976 /* PFCOUNT */
redis_pfcount_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)1977 int redis_pfcount_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1978                       char **cmd, int *cmd_len, short *slot, void **ctx)
1979 {
1980     zval *z_keys, *z_key;
1981     HashTable *ht_keys;
1982     smart_string cmdstr = {0};
1983     int num_keys, key_free;
1984     size_t key_len;
1985     char *key;
1986     short kslot=-1;
1987     zend_string *zstr;
1988 
1989     if (zend_parse_parameters(ZEND_NUM_ARGS(),"z",&z_keys) == FAILURE) {
1990         return FAILURE;
1991     }
1992 
1993     /* If we were passed an array of keys, iterate through them prefixing if
1994      * required and capturing lengths and if we need to free them.  Otherwise
1995      * attempt to treat the argument as a string and just pass one */
1996     if (Z_TYPE_P(z_keys) == IS_ARRAY) {
1997         /* Grab key hash table and the number of keys */
1998         ht_keys = Z_ARRVAL_P(z_keys);
1999         num_keys = zend_hash_num_elements(ht_keys);
2000 
2001         /* There is no reason to send zero keys */
2002         if (num_keys == 0) {
2003             return FAILURE;
2004         }
2005 
2006         /* Initialize the command with our number of arguments */
2007         redis_cmd_init_sstr(&cmdstr, num_keys, "PFCOUNT", sizeof("PFCOUNT")-1);
2008 
2009         /* Append our key(s) */
2010         ZEND_HASH_FOREACH_VAL(ht_keys, z_key) {
2011             /* Turn our value into a string if it isn't one */
2012             zstr = zval_get_string(z_key);
2013             key = ZSTR_VAL(zstr);
2014             key_len = ZSTR_LEN(zstr);
2015 
2016             /* Append this key to our command */
2017             key_free = redis_key_prefix(redis_sock, &key, &key_len);
2018             redis_cmd_append_sstr(&cmdstr, key, key_len);
2019 
2020             /* Protect against CROSSLOT errors */
2021             if (slot) {
2022                 if (kslot == -1) {
2023                     kslot = cluster_hash_key(key, key_len);
2024                 } else if (cluster_hash_key(key,key_len)!=kslot) {
2025                     zend_string_release(zstr);
2026                     if (key_free) efree(key);
2027                     efree(cmdstr.c);
2028 
2029                     php_error_docref(NULL, E_WARNING,
2030                         "Not all keys hash to the same slot!");
2031                     return FAILURE;
2032                 }
2033             }
2034 
2035             /* Cleanup */
2036             zend_string_release(zstr);
2037             if (key_free) efree(key);
2038         } ZEND_HASH_FOREACH_END();
2039     } else {
2040         /* Construct our whole command */
2041         redis_cmd_init_sstr(&cmdstr, 1, "PFCOUNT", sizeof("PFCOUNT")-1);
2042 
2043         /* Turn our key into a string if it's a different type */
2044         zstr = zval_get_string(z_keys);
2045         key = ZSTR_VAL(zstr);
2046         key_len = ZSTR_LEN(zstr);
2047         key_free = redis_key_prefix(redis_sock, &key, &key_len);
2048         redis_cmd_append_sstr(&cmdstr, key, key_len);
2049 
2050         /* Hash our key */
2051         CMD_SET_SLOT(slot, key, key_len);
2052 
2053         /* Cleanup */
2054         zend_string_release(zstr);
2055         if (key_free) efree(key);
2056     }
2057 
2058     /* Push our command and length to the caller */
2059     *cmd = cmdstr.c;
2060     *cmd_len = cmdstr.len;
2061 
2062     return SUCCESS;
2063 }
2064 
redis_variadic_str_cmd(char * kw,zval * argv,int argc,int * cmd_len)2065 char *redis_variadic_str_cmd(char *kw, zval *argv, int argc, int *cmd_len) {
2066     smart_string cmdstr = {0};
2067     zend_string *zstr;
2068     int i;
2069 
2070     redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
2071 
2072     for (i = 0; i < argc; i++) {
2073         zstr = zval_get_string(&argv[i]);
2074         redis_cmd_append_sstr_zstr(&cmdstr, zstr);
2075         zend_string_release(zstr);
2076     }
2077 
2078     *cmd_len = cmdstr.len;
2079     return cmdstr.c;
2080 }
2081 
redis_auth_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2082 int redis_auth_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2083                    char **cmd, int *cmd_len, short *slot, void **ctx)
2084 {
2085     zend_string *user = NULL, *pass = NULL;
2086     zval *ztest;
2087 
2088     if (zend_parse_parameters(ZEND_NUM_ARGS(), "z!", &ztest) == FAILURE ||
2089         redis_extract_auth_info(ztest, &user, &pass) == FAILURE)
2090     {
2091         return FAILURE;
2092     }
2093 
2094     /* Construct either AUTH <user> <pass> or AUTH <pass> */
2095     if (user && pass) {
2096         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "AUTH", "SS", user, pass);
2097     } else {
2098         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "AUTH", "S", pass);
2099     }
2100 
2101     redis_sock_set_auth(redis_sock, user, pass);
2102 
2103     if (user) zend_string_release(user);
2104     if (pass) zend_string_release(pass);
2105 
2106     return SUCCESS;
2107 }
2108 
2109 /* SETBIT */
redis_setbit_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2110 int redis_setbit_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2111                      char **cmd, int *cmd_len, short *slot, void **ctx)
2112 {
2113     char *key;
2114     size_t key_len;
2115     zend_long offset;
2116     zend_bool val;
2117 
2118     if (zend_parse_parameters(ZEND_NUM_ARGS(), "slb", &key, &key_len,
2119                              &offset, &val) == FAILURE)
2120     {
2121         return FAILURE;
2122     }
2123 
2124     // Validate our offset
2125     if (offset < BITOP_MIN_OFFSET || offset > BITOP_MAX_OFFSET) {
2126         php_error_docref(0, E_WARNING,
2127             "Invalid OFFSET for bitop command (must be between 0-2^32-1)");
2128         return FAILURE;
2129     }
2130 
2131     *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SETBIT", "kld", key, key_len, offset, (int)val);
2132 
2133     return SUCCESS;
2134 }
2135 
2136 /* LINSERT */
redis_linsert_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2137 int redis_linsert_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2138                       char **cmd, int *cmd_len, short *slot, void **ctx)
2139 {
2140     char *key, *pos;
2141     size_t key_len, pos_len;
2142     zval *z_val, *z_pivot;
2143 
2144     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sszz", &key, &key_len,
2145                              &pos, &pos_len, &z_pivot, &z_val) == FAILURE)
2146     {
2147         return FAILURE;
2148     }
2149 
2150     // Validate position
2151     if (strncasecmp(pos, "after", 5) && strncasecmp(pos, "before", 6)) {
2152         php_error_docref(NULL, E_WARNING,
2153             "Position must be either 'BEFORE' or 'AFTER'");
2154         return FAILURE;
2155     }
2156 
2157     /* Construct command */
2158     *cmd_len = REDIS_CMD_SPPRINTF(cmd, "LINSERT", "ksvv", key, key_len, pos,
2159                                  pos_len, z_pivot, z_val);
2160 
2161     // Success
2162     return SUCCESS;
2163 }
2164 
2165 /* LREM */
redis_lrem_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2166 int redis_lrem_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2167                    char **cmd, int *cmd_len, short *slot, void **ctx)
2168 {
2169     char *key;
2170     size_t key_len;
2171     zend_long count = 0;
2172     zval *z_val;
2173 
2174     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz|l", &key, &key_len,
2175                              &z_val, &count) == FAILURE)
2176     {
2177         return FAILURE;
2178     }
2179 
2180     /* Construct command */
2181     *cmd_len = REDIS_CMD_SPPRINTF(cmd, "LREM", "kdv", key, key_len, count, z_val);
2182 
2183     // Success!
2184     return SUCCESS;
2185 }
2186 
redis_smove_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2187 int redis_smove_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2188                     char **cmd, int *cmd_len, short *slot, void **ctx)
2189 {
2190     char *src, *dst;
2191     size_t src_len, dst_len;
2192     int src_free, dst_free;
2193     zval *z_val;
2194 
2195     if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssz", &src, &src_len,
2196                              &dst, &dst_len, &z_val) == FAILURE)
2197     {
2198         return FAILURE;
2199     }
2200 
2201     src_free = redis_key_prefix(redis_sock, &src, &src_len);
2202     dst_free = redis_key_prefix(redis_sock, &dst, &dst_len);
2203 
2204     // Protect against a CROSSSLOT error
2205     if (slot) {
2206         short slot1 = cluster_hash_key(src, src_len);
2207         short slot2 = cluster_hash_key(dst, dst_len);
2208         if (slot1 != slot2) {
2209             php_error_docref(0, E_WARNING,
2210                 "Source and destination keys don't hash to the same slot!");
2211             if (src_free) efree(src);
2212             if (dst_free) efree(dst);
2213             return FAILURE;
2214         }
2215         *slot = slot1;
2216     }
2217 
2218     // Construct command
2219     *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SMOVE", "ssv", src, src_len, dst,
2220         dst_len, z_val);
2221 
2222     // Cleanup
2223     if (src_free) efree(src);
2224     if (dst_free) efree(dst);
2225 
2226     // Success!
2227     return SUCCESS;
2228 }
2229 
2230 /* Generic command construction for HSET and HSETNX */
gen_hset_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot)2231 static int gen_hset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2232                         char *kw, char **cmd, int *cmd_len, short *slot)
2233 {
2234     char *key, *mem;
2235     size_t key_len, mem_len;
2236     zval *z_val;
2237 
2238     if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssz", &key, &key_len,
2239                              &mem, &mem_len, &z_val) == FAILURE)
2240     {
2241         return FAILURE;
2242     }
2243 
2244     /* Construct command */
2245     *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksv", key, key_len, mem, mem_len, z_val);
2246 
2247     // Success
2248     return SUCCESS;
2249 }
2250 
2251 /* HSET */
redis_hset_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2252 int redis_hset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2253                    char **cmd, int *cmd_len, short *slot, void **ctx)
2254 {
2255     return gen_hset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "HSET",
2256         cmd, cmd_len, slot);
2257 }
2258 
2259 /* HSETNX */
redis_hsetnx_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2260 int redis_hsetnx_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2261                      char **cmd, int *cmd_len, short *slot, void **ctx)
2262 {
2263     return gen_hset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "HSETNX",
2264         cmd, cmd_len, slot);
2265 }
2266 
2267 /* SRANDMEMBER */
redis_srandmember_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx,short * have_count)2268 int redis_srandmember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2269                           char **cmd, int *cmd_len, short *slot, void **ctx,
2270                           short *have_count)
2271 {
2272     char *key;
2273     size_t key_len;
2274     zend_long count;
2275 
2276     if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &key, &key_len,
2277                              &count) == FAILURE)
2278     {
2279         return FAILURE;
2280     }
2281 
2282     // Set our have count flag
2283     *have_count = ZEND_NUM_ARGS() == 2;
2284 
2285     // Two args means we have the optional COUNT
2286     if (*have_count) {
2287         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SRANDMEMBER", "kl", key, key_len, count);
2288     } else {
2289         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SRANDMEMBER", "k", key, key_len);
2290     }
2291 
2292     return SUCCESS;
2293 }
2294 
2295 /* ZINCRBY */
redis_zincrby_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2296 int redis_zincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2297                       char **cmd, int *cmd_len, short *slot, void **ctx)
2298 {
2299     char *key;
2300     size_t key_len;
2301     double incrby;
2302     zval *z_val;
2303 
2304     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sdz", &key, &key_len,
2305                              &incrby, &z_val) == FAILURE)
2306     {
2307         return FAILURE;
2308     }
2309 
2310     *cmd_len = REDIS_CMD_SPPRINTF(cmd, "ZINCRBY", "kfv", key, key_len, incrby, z_val);
2311 
2312     return SUCCESS;
2313 }
2314 
2315 /* SORT */
redis_sort_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,int * using_store,char ** cmd,int * cmd_len,short * slot,void ** ctx)2316 int redis_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2317                    int *using_store, char **cmd, int *cmd_len, short *slot,
2318                    void **ctx)
2319 {
2320     zval *z_opts=NULL, *z_ele, z_argv;
2321     char *key;
2322     HashTable *ht_opts;
2323     smart_string cmdstr = {0};
2324     size_t key_len;
2325     int key_free;
2326 
2327     if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|a", &key, &key_len,
2328                              &z_opts) == FAILURE)
2329     {
2330         return FAILURE;
2331     }
2332 
2333     // Default that we're not using store
2334     *using_store = 0;
2335 
2336     // If we don't have an options array, the command is quite simple
2337     if (!z_opts || zend_hash_num_elements(Z_ARRVAL_P(z_opts)) == 0) {
2338         // Construct command
2339         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SORT", "k", key, key_len);
2340 
2341         /* Not storing */
2342         *using_store = 0;
2343 
2344         return SUCCESS;
2345     }
2346 
2347     // Create our hash table to hold our sort arguments
2348     array_init(&z_argv);
2349 
2350     // SORT <key>
2351     key_free = redis_key_prefix(redis_sock, &key, &key_len);
2352     add_next_index_stringl(&z_argv, key, key_len);
2353     if (key_free) efree(key);
2354 
2355     // Set slot
2356     CMD_SET_SLOT(slot,key,key_len);
2357 
2358     // Grab the hash table
2359     ht_opts = Z_ARRVAL_P(z_opts);
2360 
2361     // Handle BY pattern
2362     if (((z_ele = zend_hash_str_find(ht_opts, "by", sizeof("by") - 1)) != NULL ||
2363          (z_ele = zend_hash_str_find(ht_opts, "BY", sizeof("BY") - 1)) != NULL
2364         ) && Z_TYPE_P(z_ele) == IS_STRING
2365     ) {
2366         // "BY" option is disabled in cluster
2367         if (slot) {
2368             php_error_docref(NULL, E_WARNING,
2369                 "SORT BY option is not allowed in Redis Cluster");
2370             zval_dtor(&z_argv);
2371             return FAILURE;
2372         }
2373 
2374         // ... BY <pattern>
2375         add_next_index_stringl(&z_argv, "BY", sizeof("BY") - 1);
2376         add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
2377     }
2378 
2379     // Handle ASC/DESC option
2380     if (((z_ele = zend_hash_str_find(ht_opts, "sort", sizeof("sort") - 1)) != NULL ||
2381          (z_ele = zend_hash_str_find(ht_opts, "SORT", sizeof("SORT") - 1)) != NULL
2382         ) && Z_TYPE_P(z_ele) == IS_STRING
2383     ) {
2384         // 'asc'|'desc'
2385         add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
2386     }
2387 
2388     // STORE option
2389     if (((z_ele = zend_hash_str_find(ht_opts, "store", sizeof("store") - 1)) != NULL ||
2390          (z_ele = zend_hash_str_find(ht_opts, "STORE", sizeof("STORE") - 1)) != NULL
2391         ) && Z_TYPE_P(z_ele) == IS_STRING
2392     ) {
2393         // Slot verification
2394         int cross_slot = slot && *slot != cluster_hash_key(
2395             Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
2396 
2397         if (cross_slot) {
2398             php_error_docref(0, E_WARNING,
2399                 "Error, SORT key and STORE key have different slots!");
2400             zval_dtor(&z_argv);
2401             return FAILURE;
2402         }
2403 
2404         // STORE <key>
2405         add_next_index_stringl(&z_argv, "STORE", sizeof("STORE") - 1);
2406         add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
2407 
2408         // We are using STORE
2409         *using_store = 1;
2410     }
2411 
2412     // GET option
2413     if (((z_ele = zend_hash_str_find(ht_opts, "get", sizeof("get") - 1)) != NULL ||
2414          (z_ele = zend_hash_str_find(ht_opts, "GET", sizeof("GET") - 1)) != NULL
2415         ) && (Z_TYPE_P(z_ele) == IS_STRING || Z_TYPE_P(z_ele) == IS_ARRAY)
2416     ) {
2417         // Disabled in cluster
2418         if (slot) {
2419             php_error_docref(NULL, E_WARNING,
2420                 "GET option for SORT disabled in Redis Cluster");
2421             zval_dtor(&z_argv);
2422             return FAILURE;
2423         }
2424 
2425         // If it's a string just add it
2426         if (Z_TYPE_P(z_ele) == IS_STRING) {
2427             add_next_index_stringl(&z_argv, "GET", sizeof("GET") - 1);
2428             add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
2429         } else {
2430             int added = 0;
2431             zval *z_key;
2432 
2433             ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_ele), z_key) {
2434                 // If we can't get the data, or it's not a string, skip
2435                 if (z_key == NULL || Z_TYPE_P(z_key) != IS_STRING) {
2436                     continue;
2437                 }
2438                 /* Add get per thing we're getting */
2439                 add_next_index_stringl(&z_argv, "GET", sizeof("GET") - 1);
2440 
2441                 // Add this key to our argv array
2442                 add_next_index_stringl(&z_argv, Z_STRVAL_P(z_key), Z_STRLEN_P(z_key));
2443                 added++;
2444             } ZEND_HASH_FOREACH_END();
2445 
2446             // Make sure we were able to add at least one
2447             if (added == 0) {
2448                 php_error_docref(NULL, E_WARNING,
2449                     "Array of GET values requested, but none are valid");
2450                 zval_dtor(&z_argv);
2451                 return FAILURE;
2452             }
2453         }
2454     }
2455 
2456     // ALPHA
2457     if (((z_ele = zend_hash_str_find(ht_opts, "alpha", sizeof("alpha") - 1)) != NULL ||
2458          (z_ele = zend_hash_str_find(ht_opts, "ALPHA", sizeof("ALPHA") - 1)) != NULL) &&
2459          zval_is_true(z_ele)
2460     ) {
2461         add_next_index_stringl(&z_argv, "ALPHA", sizeof("ALPHA") - 1);
2462     }
2463 
2464     // LIMIT <offset> <count>
2465     if (((z_ele = zend_hash_str_find(ht_opts, "limit", sizeof("limit") - 1)) != NULL ||
2466          (z_ele = zend_hash_str_find(ht_opts, "LIMIT", sizeof("LIMIT") - 1)) != NULL
2467         ) && Z_TYPE_P(z_ele) == IS_ARRAY
2468     ) {
2469         HashTable *ht_off = Z_ARRVAL_P(z_ele);
2470         zval *z_off, *z_cnt;
2471 
2472         if ((z_off = zend_hash_index_find(ht_off, 0)) != NULL &&
2473             (z_cnt = zend_hash_index_find(ht_off, 1)) != NULL
2474         ) {
2475             if ((Z_TYPE_P(z_off) != IS_STRING && Z_TYPE_P(z_off) != IS_LONG) ||
2476                 (Z_TYPE_P(z_cnt) != IS_STRING && Z_TYPE_P(z_cnt) != IS_LONG)
2477             ) {
2478                 php_error_docref(NULL, E_WARNING,
2479                     "LIMIT options on SORT command must be longs or strings");
2480                 zval_dtor(&z_argv);
2481                 return FAILURE;
2482             }
2483 
2484             // Add LIMIT argument
2485             add_next_index_stringl(&z_argv, "LIMIT", sizeof("LIMIT") - 1);
2486 
2487             long low, high;
2488             if (Z_TYPE_P(z_off) == IS_STRING) {
2489                 low = atol(Z_STRVAL_P(z_off));
2490             } else {
2491                 low = Z_LVAL_P(z_off);
2492             }
2493             if (Z_TYPE_P(z_cnt) == IS_STRING) {
2494                 high = atol(Z_STRVAL_P(z_cnt));
2495             } else {
2496                 high = Z_LVAL_P(z_cnt);
2497             }
2498 
2499             // Add our two LIMIT arguments
2500             add_next_index_long(&z_argv, low);
2501             add_next_index_long(&z_argv, high);
2502         }
2503     }
2504 
2505     // Start constructing our command
2506     HashTable *ht_argv = Z_ARRVAL_P(&z_argv);
2507     redis_cmd_init_sstr(&cmdstr, zend_hash_num_elements(ht_argv), "SORT",
2508         sizeof("SORT")-1);
2509 
2510     // Iterate through our arguments
2511     ZEND_HASH_FOREACH_VAL(ht_argv, z_ele) {
2512         // Args are strings or longs
2513         if (Z_TYPE_P(z_ele) == IS_STRING) {
2514             redis_cmd_append_sstr(&cmdstr,Z_STRVAL_P(z_ele),
2515                 Z_STRLEN_P(z_ele));
2516         } else {
2517             redis_cmd_append_sstr_long(&cmdstr, Z_LVAL_P(z_ele));
2518         }
2519     } ZEND_HASH_FOREACH_END();
2520 
2521     /* Clean up our arguments array.  Note we don't have to free any prefixed
2522      * key as that we didn't duplicate the pointer if we prefixed */
2523     zval_dtor(&z_argv);
2524 
2525     // Push our length and command
2526     *cmd_len = cmdstr.len;
2527     *cmd     = cmdstr.c;
2528 
2529     // Success!
2530     return SUCCESS;
2531 }
2532 
2533 /* HDEL */
redis_hdel_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2534 int redis_hdel_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2535                    char **cmd, int *cmd_len, short *slot, void **ctx)
2536 {
2537     zval *z_args;
2538     smart_string cmdstr = {0};
2539     char *arg;
2540     int arg_free, i;
2541     size_t arg_len;
2542     int argc = ZEND_NUM_ARGS();
2543     zend_string *zstr;
2544 
2545     // We need at least KEY and one member
2546     if (argc < 2) {
2547         return FAILURE;
2548     }
2549 
2550     // Grab arguments as an array
2551     z_args = emalloc(argc * sizeof(zval));
2552     if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
2553         efree(z_args);
2554         return FAILURE;
2555     }
2556 
2557     // Get first argument (the key) as a string
2558     zstr = zval_get_string(&z_args[0]);
2559     arg = ZSTR_VAL(zstr);
2560     arg_len = ZSTR_LEN(zstr);
2561 
2562     // Prefix
2563     arg_free = redis_key_prefix(redis_sock, &arg, &arg_len);
2564 
2565     // Start command construction
2566     redis_cmd_init_sstr(&cmdstr, argc, "HDEL", sizeof("HDEL")-1);
2567     redis_cmd_append_sstr(&cmdstr, arg, arg_len);
2568 
2569     // Set our slot, free key if we prefixed it
2570     CMD_SET_SLOT(slot,arg,arg_len);
2571     zend_string_release(zstr);
2572     if (arg_free) efree(arg);
2573 
2574     // Iterate through the members we're removing
2575     for (i = 1; i < argc; i++) {
2576         zstr = zval_get_string(&z_args[i]);
2577         redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
2578         zend_string_release(zstr);
2579     }
2580 
2581     // Push out values
2582     *cmd     = cmdstr.c;
2583     *cmd_len = cmdstr.len;
2584 
2585     // Cleanup
2586     efree(z_args);
2587 
2588     // Success!
2589     return SUCCESS;
2590 }
2591 
2592 /* ZADD */
redis_zadd_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2593 int redis_zadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2594                    char **cmd, int *cmd_len, short *slot, void **ctx)
2595 {
2596     zval *z_args;
2597     char *key, *val, *exp_type = NULL;
2598     size_t key_len, val_len;
2599     int key_free, val_free;
2600     int num = ZEND_NUM_ARGS(), i = 1, argc;
2601     zend_bool ch = 0, incr = 0;
2602     smart_string cmdstr = {0};
2603     zend_string *zstr;
2604 
2605     if (num < 3) return FAILURE;
2606     z_args = ecalloc(num, sizeof(zval));
2607     if (zend_get_parameters_array(ht, num, z_args) == FAILURE) {
2608         efree(z_args);
2609         return FAILURE;
2610     }
2611 
2612     // Need key, [NX|XX] [CH] [INCR] score, value, [score, value...] */
2613     if (num % 2 == 0) {
2614         if (Z_TYPE(z_args[1]) != IS_ARRAY) {
2615             efree(z_args);
2616             return FAILURE;
2617         }
2618         zval *z_opt;
2619         ZEND_HASH_FOREACH_VAL(Z_ARRVAL(z_args[1]), z_opt) {
2620             if (Z_TYPE_P(z_opt) == IS_STRING) {
2621                 if (ZVAL_IS_NX_XX_ARG(z_opt)) {
2622                     exp_type = Z_STRVAL_P(z_opt);
2623                 } else if (ZVAL_STRICMP_STATIC(z_opt, "CH")) {
2624                     ch = 1;
2625                 } else if (ZVAL_STRICMP_STATIC(z_opt, "INCR")) {
2626                     if (num > 4) {
2627                         // Only one score-element pair can be specified in this mode.
2628                         efree(z_args);
2629                         return FAILURE;
2630                     }
2631                     incr = 1;
2632                 }
2633 
2634             }
2635         } ZEND_HASH_FOREACH_END();
2636         argc  = num - 1;
2637         if (exp_type) argc++;
2638         argc += ch + incr;
2639         i++;
2640     } else {
2641         argc = num;
2642     }
2643 
2644     // Prefix our key
2645     zstr = zval_get_string(&z_args[0]);
2646     key = ZSTR_VAL(zstr);
2647     key_len = ZSTR_LEN(zstr);
2648     key_free = redis_key_prefix(redis_sock, &key, &key_len);
2649 
2650     // Start command construction
2651     redis_cmd_init_sstr(&cmdstr, argc, "ZADD", sizeof("ZADD")-1);
2652     redis_cmd_append_sstr(&cmdstr, key, key_len);
2653 
2654     // Set our slot, free key if we prefixed it
2655     CMD_SET_SLOT(slot,key,key_len);
2656     zend_string_release(zstr);
2657     if (key_free) efree(key);
2658 
2659     if (exp_type) redis_cmd_append_sstr(&cmdstr, exp_type, 2);
2660     if (ch) redis_cmd_append_sstr(&cmdstr, "CH", 2);
2661     if (incr) redis_cmd_append_sstr(&cmdstr, "INCR", 4);
2662 
2663     // Now the rest of our arguments
2664     while (i < num) {
2665         // Append score and member
2666         switch (Z_TYPE(z_args[i])) {
2667         case IS_LONG:
2668         case IS_DOUBLE:
2669             redis_cmd_append_sstr_dbl(&cmdstr, zval_get_double(&z_args[i]));
2670             break;
2671         case IS_STRING:
2672             /* The score values must be the string representation of a double
2673              * precision floating point number. +inf and -inf values are valid
2674              * values as well. */
2675             if (strncasecmp(Z_STRVAL(z_args[i]), "-inf", 4) == 0 ||
2676                 strncasecmp(Z_STRVAL(z_args[i]), "+inf", 4) == 0 ||
2677                 is_numeric_string(Z_STRVAL(z_args[i]), Z_STRLEN(z_args[i]), NULL, NULL, 0) != 0
2678             ) {
2679                 redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[i]), Z_STRLEN(z_args[i]));
2680                 break;
2681             }
2682             // fall through
2683         default:
2684             php_error_docref(NULL, E_WARNING, "Scores must be numeric or '-inf','+inf'");
2685             smart_string_free(&cmdstr);
2686             efree(z_args);
2687             return FAILURE;
2688         }
2689         // serialize value if requested
2690         val_free = redis_pack(redis_sock, &z_args[i+1], &val, &val_len);
2691         redis_cmd_append_sstr(&cmdstr, val, val_len);
2692 
2693         // Free value if we serialized
2694         if (val_free) efree(val);
2695         i += 2;
2696     }
2697 
2698     // Push output values
2699     *cmd     = cmdstr.c;
2700     *cmd_len = cmdstr.len;
2701 
2702     // Cleanup args
2703     efree(z_args);
2704 
2705     return SUCCESS;
2706 }
2707 
2708 /* OBJECT */
redis_object_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,REDIS_REPLY_TYPE * rtype,char ** cmd,int * cmd_len,short * slot,void ** ctx)2709 int redis_object_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2710                      REDIS_REPLY_TYPE *rtype, char **cmd, int *cmd_len,
2711                      short *slot, void **ctx)
2712 {
2713     char *key, *subcmd;
2714     size_t key_len, subcmd_len;
2715 
2716     if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &subcmd,
2717                              &subcmd_len, &key, &key_len) == FAILURE)
2718     {
2719         return FAILURE;
2720     }
2721 
2722     // Format our command
2723     *cmd_len = REDIS_CMD_SPPRINTF(cmd, "OBJECT", "sk", subcmd, subcmd_len, key, key_len);
2724 
2725     // Push the reply type to our caller
2726     if (subcmd_len == 8 && (!strncasecmp(subcmd,"refcount",8) ||
2727                            !strncasecmp(subcmd,"idletime",8)))
2728     {
2729         *rtype = TYPE_INT;
2730     } else if (subcmd_len == 8 && !strncasecmp(subcmd, "encoding", 8)) {
2731         *rtype = TYPE_BULK;
2732     } else {
2733         php_error_docref(NULL, E_WARNING,
2734             "Invalid subcommand sent to OBJECT");
2735         efree(*cmd);
2736         return FAILURE;
2737     }
2738 
2739     // Success
2740     return SUCCESS;
2741 }
2742 
2743 /* GEODIST */
redis_geodist_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)2744 int redis_geodist_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2745                       char **cmd, int *cmd_len, short *slot, void **ctx)
2746 {
2747     char *key, *source, *dest, *unit = NULL;
2748     size_t keylen, sourcelen, destlen, unitlen;
2749 
2750     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|s", &key, &keylen,
2751                               &source, &sourcelen, &dest, &destlen, &unit,
2752                               &unitlen) == FAILURE)
2753     {
2754         return FAILURE;
2755     }
2756 
2757     /* Construct command */
2758     if (unit != NULL) {
2759         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "GEODIST", "ksss", key, keylen, source,
2760                                      sourcelen, dest, destlen, unit, unitlen);
2761     } else {
2762         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "GEODIST", "kss", key, keylen, source,
2763                                      sourcelen, dest, destlen);
2764     }
2765 
2766     return SUCCESS;
2767 }
2768 
get_georadius_store_type(zend_string * key)2769 geoStoreType get_georadius_store_type(zend_string *key) {
2770     if (ZSTR_LEN(key) == 5 && !strcasecmp(ZSTR_VAL(key), "store")) {
2771         return STORE_COORD;
2772     } else if (ZSTR_LEN(key) == 9 && !strcasecmp(ZSTR_VAL(key), "storedist")) {
2773         return STORE_DIST;
2774     }
2775 
2776     return STORE_NONE;
2777 }
2778 
2779 /* Helper function to extract optional arguments for GEORADIUS and GEORADIUSBYMEMBER */
get_georadius_opts(HashTable * ht,geoOptions * opts)2780 static int get_georadius_opts(HashTable *ht, geoOptions *opts) {
2781     char *optstr;
2782     zend_string *zkey;
2783     zval *optval;
2784 
2785     /* Iterate over our argument array, collating which ones we have */
2786     ZEND_HASH_FOREACH_STR_KEY_VAL(ht, zkey, optval) {
2787         ZVAL_DEREF(optval);
2788 
2789         /* If the key is numeric it's a non value option */
2790         if (zkey) {
2791             if (ZSTR_LEN(zkey) == 5 && !strcasecmp(ZSTR_VAL(zkey), "count")) {
2792                 if (Z_TYPE_P(optval) != IS_LONG || Z_LVAL_P(optval) <= 0) {
2793                     php_error_docref(NULL, E_WARNING,
2794                             "COUNT must be an integer > 0!");
2795                     if (opts->key) zend_string_release(opts->key);
2796                     return FAILURE;
2797                 }
2798 
2799                 /* Set our count */
2800                 opts->count = Z_LVAL_P(optval);
2801             } else if (opts->store == STORE_NONE) {
2802                 opts->store = get_georadius_store_type(zkey);
2803                 if (opts->store != STORE_NONE) {
2804                     opts->key = zval_get_string(optval);
2805                 }
2806             }
2807         } else {
2808             /* Option needs to be a string */
2809             if (Z_TYPE_P(optval) != IS_STRING) continue;
2810 
2811             optstr = Z_STRVAL_P(optval);
2812 
2813             if (!strcasecmp(optstr, "withcoord")) {
2814                 opts->withcoord = 1;
2815             } else if (!strcasecmp(optstr, "withdist")) {
2816                 opts->withdist = 1;
2817             } else if (!strcasecmp(optstr, "withhash")) {
2818                 opts->withhash = 1;
2819             } else if (!strcasecmp(optstr, "asc")) {
2820                 opts->sort = SORT_ASC;
2821             } else if (!strcasecmp(optstr, "desc")) {
2822                 opts->sort = SORT_DESC;
2823             }
2824         }
2825     } ZEND_HASH_FOREACH_END();
2826 
2827     /* STORE and STOREDIST are not compatible with the WITH* options */
2828     if (opts->key != NULL && (opts->withcoord || opts->withdist || opts->withhash)) {
2829         php_error_docref(NULL, E_WARNING,
2830             "STORE[DIST] is not compatible with WITHCOORD, WITHDIST or WITHHASH");
2831 
2832         if (opts->key) zend_string_release(opts->key);
2833         return FAILURE;
2834     }
2835 
2836     /* Success */
2837     return SUCCESS;
2838 }
2839 
2840 /* Helper to append options to a GEORADIUS or GEORADIUSBYMEMBER command */
append_georadius_opts(RedisSock * redis_sock,smart_string * str,short * slot,geoOptions * opt)2841 void append_georadius_opts(RedisSock *redis_sock, smart_string *str, short *slot,
2842                            geoOptions *opt)
2843 {
2844     char *key;
2845     size_t keylen;
2846     int keyfree;
2847 
2848     if (opt->withcoord)
2849         REDIS_CMD_APPEND_SSTR_STATIC(str, "WITHCOORD");
2850     if (opt->withdist)
2851         REDIS_CMD_APPEND_SSTR_STATIC(str, "WITHDIST");
2852     if (opt->withhash)
2853         REDIS_CMD_APPEND_SSTR_STATIC(str, "WITHHASH");
2854 
2855     /* Append sort if it's not GEO_NONE */
2856     if (opt->sort == SORT_ASC) {
2857         REDIS_CMD_APPEND_SSTR_STATIC(str, "ASC");
2858     } else if (opt->sort == SORT_DESC) {
2859         REDIS_CMD_APPEND_SSTR_STATIC(str, "DESC");
2860     }
2861 
2862     /* Append our count if we've got one */
2863     if (opt->count) {
2864         REDIS_CMD_APPEND_SSTR_STATIC(str, "COUNT");
2865         redis_cmd_append_sstr_long(str, opt->count);
2866     }
2867 
2868     /* Append store options if we've got them */
2869     if (opt->store != STORE_NONE && opt->key != NULL) {
2870         /* Grab string bits and prefix if requested */
2871         key = ZSTR_VAL(opt->key);
2872         keylen = ZSTR_LEN(opt->key);
2873         keyfree = redis_key_prefix(redis_sock, &key, &keylen);
2874 
2875         if (opt->store == STORE_COORD) {
2876             REDIS_CMD_APPEND_SSTR_STATIC(str, "STORE");
2877         } else {
2878             REDIS_CMD_APPEND_SSTR_STATIC(str, "STOREDIST");
2879         }
2880 
2881         redis_cmd_append_sstr(str, key, keylen);
2882 
2883         CMD_SET_SLOT(slot, key, keylen);
2884         if (keyfree) free(key);
2885     }
2886 }
2887 
2888 /* GEORADIUS / GEORADIUS_RO */
redis_georadius_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)2889 int redis_georadius_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2890                         char *kw, char **cmd, int *cmd_len, short *slot,
2891                         void **ctx)
2892 {
2893     char *key, *unit;
2894     short store_slot = 0;
2895     size_t keylen, unitlen;
2896     int argc = 5, keyfree;
2897     double lng, lat, radius;
2898     zval *opts = NULL;
2899     geoOptions gopts = {0};
2900     smart_string cmdstr = {0};
2901 
2902     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sddds|a", &key, &keylen,
2903                               &lng, &lat, &radius, &unit, &unitlen, &opts)
2904                               == FAILURE)
2905     {
2906         return FAILURE;
2907     }
2908 
2909     /* Parse any GEORADIUS options we have */
2910     if (opts != NULL) {
2911         /* Attempt to parse our options array */
2912         if (get_georadius_opts(Z_ARRVAL_P(opts), &gopts) != SUCCESS)
2913         {
2914             return FAILURE;
2915         }
2916     }
2917 
2918     /* Increment argc depending on options */
2919     argc += gopts.withcoord + gopts.withdist + gopts.withhash +
2920             (gopts.sort != SORT_NONE) + (gopts.count ? 2 : 0) +
2921             (gopts.store != STORE_NONE ? 2 : 0);
2922 
2923     /* Begin construction of our command */
2924     redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
2925 
2926     /* Prefix and set slot */
2927     keyfree = redis_key_prefix(redis_sock, &key, &keylen);
2928     CMD_SET_SLOT(slot, key, keylen);
2929 
2930     /* Append required arguments */
2931     redis_cmd_append_sstr(&cmdstr, key, keylen);
2932     redis_cmd_append_sstr_dbl(&cmdstr, lng);
2933     redis_cmd_append_sstr_dbl(&cmdstr, lat);
2934     redis_cmd_append_sstr_dbl(&cmdstr, radius);
2935     redis_cmd_append_sstr(&cmdstr, unit, unitlen);
2936 
2937     /* Append optional arguments */
2938     append_georadius_opts(redis_sock, &cmdstr, slot ? &store_slot : NULL, &gopts);
2939 
2940     /* Free key if it was prefixed */
2941     if (keyfree) efree(key);
2942     if (gopts.key) zend_string_release(gopts.key);
2943 
2944     /* Protect the user from CROSSSLOT if we're in cluster */
2945     if (slot && gopts.store != STORE_NONE && *slot != store_slot) {
2946         php_error_docref(NULL, E_WARNING,
2947             "Key and STORE[DIST] key must hash to the same slot");
2948         efree(cmdstr.c);
2949         return FAILURE;
2950     }
2951 
2952     /* Set slot, command and len, and return */
2953     *cmd = cmdstr.c;
2954     *cmd_len = cmdstr.len;
2955 
2956     return SUCCESS;
2957 }
2958 
2959 /* GEORADIUSBYMEMBER/GEORADIUSBYMEMBER_RO
2960  *    key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] */
redis_georadiusbymember_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)2961 int redis_georadiusbymember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2962                                 char *kw, char **cmd, int *cmd_len, short *slot,
2963                                 void **ctx)
2964 {
2965     char *key, *mem, *unit;
2966     size_t keylen, memlen, unitlen;
2967     short store_slot = 0;
2968     int keyfree, argc = 4;
2969     double radius;
2970     geoOptions gopts = {0};
2971     zval *opts = NULL;
2972     smart_string cmdstr = {0};
2973 
2974     if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssds|a", &key, &keylen,
2975                               &mem, &memlen, &radius, &unit, &unitlen, &opts) == FAILURE)
2976     {
2977         return FAILURE;
2978     }
2979 
2980     if (opts != NULL) {
2981         /* Attempt to parse our options array */
2982         if (get_georadius_opts(Z_ARRVAL_P(opts), &gopts) == FAILURE) {
2983             return FAILURE;
2984         }
2985     }
2986 
2987     /* Increment argc based on options */
2988     argc += gopts.withcoord + gopts.withdist + gopts.withhash +
2989             (gopts.sort != SORT_NONE) + (gopts.count ? 2 : 0) +
2990             (gopts.store != STORE_NONE ? 2 : 0);
2991 
2992     /* Begin command construction*/
2993     redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
2994 
2995     /* Prefix our key if we're prefixing and set the slot */
2996     keyfree = redis_key_prefix(redis_sock, &key, &keylen);
2997     CMD_SET_SLOT(slot, key, keylen);
2998 
2999     /* Append required arguments */
3000     redis_cmd_append_sstr(&cmdstr, key, keylen);
3001     redis_cmd_append_sstr(&cmdstr, mem, memlen);
3002     redis_cmd_append_sstr_long(&cmdstr, radius);
3003     redis_cmd_append_sstr(&cmdstr, unit, unitlen);
3004 
3005     /* Append options */
3006     append_georadius_opts(redis_sock, &cmdstr, slot ? &store_slot : NULL, &gopts);
3007 
3008     /* Free key if we prefixed */
3009     if (keyfree) efree(key);
3010     if (gopts.key) zend_string_release(gopts.key);
3011 
3012     /* Protect the user from CROSSSLOT if we're in cluster */
3013     if (slot && gopts.store != STORE_NONE && *slot != store_slot) {
3014         php_error_docref(NULL, E_WARNING,
3015             "Key and STORE[DIST] key must hash to the same slot");
3016         efree(cmdstr.c);
3017         return FAILURE;
3018     }
3019 
3020     *cmd = cmdstr.c;
3021     *cmd_len = cmdstr.len;
3022 
3023     return SUCCESS;
3024 }
3025 
3026 /* MIGRATE */
redis_migrate_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3027 int redis_migrate_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3028                       char **cmd, int *cmd_len, short *slot, void **ctx)
3029 {
3030     smart_string cmdstr = {0};
3031     char *host, *key;
3032     int argc, keyfree;
3033     zval *z_keys, *z_key;
3034     size_t hostlen, keylen;
3035     zend_long destdb, port, timeout;
3036     zend_bool copy = 0, replace = 0;
3037     zend_string *zstr;
3038 
3039     if (zend_parse_parameters(ZEND_NUM_ARGS(), "slzll|bb", &host, &hostlen, &port,
3040                               &z_keys, &destdb, &timeout, &copy, &replace) == FAILURE)
3041     {
3042         return FAILURE;
3043     }
3044 
3045     /* Protect against being passed an array with zero elements */
3046     if (Z_TYPE_P(z_keys) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(z_keys)) == 0) {
3047         php_error_docref(NULL, E_WARNING, "Keys array cannot be empty");
3048         return FAILURE;
3049     }
3050 
3051     /* host, port, key|"", dest-db, timeout, [copy, replace] [KEYS key1..keyN] */
3052     argc = 5 + copy + replace;
3053     if (Z_TYPE_P(z_keys) == IS_ARRAY) {
3054         /* +1 for the "KEYS" argument itself */
3055         argc += 1 + zend_hash_num_elements(Z_ARRVAL_P(z_keys));
3056     }
3057 
3058     /* Initialize MIGRATE command with host and port */
3059     REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "MIGRATE");
3060     redis_cmd_append_sstr(&cmdstr, host, hostlen);
3061     redis_cmd_append_sstr_long(&cmdstr, port);
3062 
3063     /* If passed a keys array the keys come later, otherwise pass the key to
3064      * migrate here */
3065     if (Z_TYPE_P(z_keys) == IS_ARRAY) {
3066         REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "");
3067     } else {
3068         /* Grab passed value as a string */
3069         zstr = zval_get_string(z_keys);
3070 
3071         /* We may need to prefix our string */
3072         key = ZSTR_VAL(zstr);
3073         keylen = ZSTR_LEN(zstr);
3074         keyfree = redis_key_prefix(redis_sock, &key, &keylen);
3075 
3076         /* Add key to migrate */
3077         redis_cmd_append_sstr(&cmdstr, key, keylen);
3078 
3079         zend_string_release(zstr);
3080         if (keyfree) efree(key);
3081     }
3082 
3083     redis_cmd_append_sstr_long(&cmdstr, destdb);
3084     redis_cmd_append_sstr_long(&cmdstr, timeout);
3085     REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, copy, "COPY");
3086     REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, replace, "REPLACE");
3087 
3088     /* Append actual keys if we've got a keys array */
3089     if (Z_TYPE_P(z_keys) == IS_ARRAY) {
3090         REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "KEYS");
3091 
3092         ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_keys), z_key) {
3093             zstr = zval_get_string(z_key);
3094 
3095             key = ZSTR_VAL(zstr);
3096             keylen = ZSTR_LEN(zstr);
3097             keyfree = redis_key_prefix(redis_sock, &key, &keylen);
3098 
3099             /* Append the key */
3100             redis_cmd_append_sstr(&cmdstr, key, keylen);
3101 
3102             zend_string_release(zstr);
3103             if (keyfree) efree(key);
3104         } ZEND_HASH_FOREACH_END();
3105     }
3106 
3107     *cmd = cmdstr.c;
3108     *cmd_len = cmdstr.len;
3109     return SUCCESS;
3110 }
3111 
3112 /* EXISTS */
redis_exists_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3113 int redis_exists_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3114                      char **cmd, int *cmd_len, short *slot, void **ctx)
3115 {
3116     return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3117         "EXISTS", sizeof("EXISTS") - 1, 0, 0, cmd, cmd_len, slot);
3118 }
3119 
3120 /* DEL */
redis_del_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3121 int redis_del_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3122                   char **cmd, int *cmd_len, short *slot, void **ctx)
3123 {
3124     return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3125         "DEL", sizeof("DEL")-1, 1, 0, cmd, cmd_len, slot);
3126 }
3127 
3128 /* UNLINK */
redis_unlink_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3129 int redis_unlink_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3130                      char **cmd, int *cmd_len, short *slot, void **ctx)
3131 {
3132     return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3133         "UNLINK", sizeof("UNLINK")-1, 1, 0, cmd, cmd_len, slot);
3134 }
3135 
3136 /* WATCH */
redis_watch_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3137 int redis_watch_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3138                     char **cmd, int *cmd_len, short *slot, void **ctx)
3139 {
3140     return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3141         "WATCH", sizeof("WATCH")-1, 1, 0, cmd, cmd_len, slot);
3142 }
3143 
3144 /* SINTER */
redis_sinter_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3145 int redis_sinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3146                      char **cmd, int *cmd_len, short *slot, void **ctx)
3147 {
3148     return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3149         "SINTER", sizeof("SINTER")-1, 1, 0, cmd, cmd_len, slot);
3150 }
3151 
3152 /* SINTERSTORE */
redis_sinterstore_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3153 int redis_sinterstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3154                           char **cmd, int *cmd_len, short *slot, void **ctx)
3155 {
3156     return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3157         "SINTERSTORE", sizeof("SINTERSTORE")-1, 1, 0, cmd, cmd_len, slot);
3158 }
3159 
3160 /* SUNION */
redis_sunion_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3161 int redis_sunion_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3162                      char **cmd, int *cmd_len, short *slot, void **ctx)
3163 {
3164     return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3165         "SUNION", sizeof("SUNION")-1, 1, 0, cmd, cmd_len, slot);
3166 }
3167 
3168 /* SUNIONSTORE */
redis_sunionstore_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3169 int redis_sunionstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3170                           char **cmd, int *cmd_len, short *slot, void **ctx)
3171 {
3172     return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3173         "SUNIONSTORE", sizeof("SUNIONSTORE")-1, 2, 0, cmd, cmd_len, slot);
3174 }
3175 
3176 /* SDIFF */
redis_sdiff_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3177 int redis_sdiff_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3178                     char **cmd, int *cmd_len, short *slot, void **ctx)
3179 {
3180     return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "SDIFF",
3181         sizeof("SDIFF")-1, 1, 0, cmd, cmd_len, slot);
3182 }
3183 
3184 /* SDIFFSTORE */
redis_sdiffstore_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3185 int redis_sdiffstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3186                          char **cmd, int *cmd_len, short *slot, void **ctx)
3187 {
3188     return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3189         "SDIFFSTORE", sizeof("SDIFFSTORE")-1, 1, 0, cmd, cmd_len, slot);
3190 }
3191 
3192 /* COMMAND */
redis_command_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3193 int redis_command_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3194                       char **cmd, int *cmd_len, short *slot, void **ctx)
3195 {
3196     char *kw=NULL;
3197     zval *z_arg;
3198     size_t kw_len;
3199 
3200     /* Parse our args */
3201     if (zend_parse_parameters(ZEND_NUM_ARGS(), "|sz", &kw, &kw_len,
3202                              &z_arg) == FAILURE)
3203     {
3204         return FAILURE;
3205     }
3206 
3207     /* Construct our command */
3208     if (!kw) {
3209         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "COMMAND", "");
3210     } else if (!z_arg) {
3211         /* Sanity check */
3212         if (strncasecmp(kw, "count", sizeof("count") - 1)) {
3213             return FAILURE;
3214         }
3215         /* COMMAND COUNT */
3216         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "COMMAND", "s", "COUNT", sizeof("COUNT") - 1);
3217     } else if (Z_TYPE_P(z_arg) == IS_STRING) {
3218         /* Sanity check */
3219         if (strncasecmp(kw, "info", sizeof("info") - 1)) {
3220             return FAILURE;
3221         }
3222 
3223         /* COMMAND INFO <cmd> */
3224         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "COMMAND", "ss", "INFO", sizeof("INFO") - 1,
3225             Z_STRVAL_P(z_arg), Z_STRLEN_P(z_arg));
3226     } else {
3227         int arr_len;
3228 
3229         /* Sanity check on args */
3230         if (strncasecmp(kw, "getkeys", sizeof("getkeys")-1) ||
3231            Z_TYPE_P(z_arg)!=IS_ARRAY ||
3232            (arr_len=zend_hash_num_elements(Z_ARRVAL_P(z_arg)))<1)
3233         {
3234             return FAILURE;
3235         }
3236 
3237         zval *z_ele;
3238         HashTable *ht_arr = Z_ARRVAL_P(z_arg);
3239         smart_string cmdstr = {0};
3240 
3241         redis_cmd_init_sstr(&cmdstr, 1 + arr_len, "COMMAND", sizeof("COMMAND")-1);
3242         redis_cmd_append_sstr(&cmdstr, "GETKEYS", sizeof("GETKEYS")-1);
3243 
3244         ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
3245             zend_string *zstr = zval_get_string(z_ele);
3246             redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
3247             zend_string_release(zstr);
3248         } ZEND_HASH_FOREACH_END();
3249 
3250         *cmd = cmdstr.c;
3251         *cmd_len = cmdstr.len;
3252     }
3253 
3254     /* Any slot will do */
3255     CMD_RAND_SLOT(slot);
3256 
3257     return SUCCESS;
3258 }
3259 
3260 /* XADD */
redis_xadd_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3261 int redis_xadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3262                    char **cmd, int *cmd_len, short *slot, void **ctx)
3263 {
3264     smart_string cmdstr = {0};
3265     zend_string *arrkey;
3266     zval *z_fields, *value;
3267     zend_long maxlen = 0;
3268     zend_bool approx = 0;
3269     zend_ulong idx;
3270     HashTable *ht_fields;
3271     int fcount, argc;
3272     char *key, *id;
3273     size_t keylen, idlen;
3274 
3275     if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssa|lb", &key, &keylen,
3276                               &id, &idlen, &z_fields, &maxlen, &approx) == FAILURE)
3277     {
3278         return FAILURE;
3279     }
3280 
3281     /* At least one field and string are required */
3282     ht_fields = Z_ARRVAL_P(z_fields);
3283     if ((fcount = zend_hash_num_elements(ht_fields)) == 0) {
3284         return FAILURE;
3285     }
3286 
3287     if (maxlen < 0 || (maxlen == 0 && approx != 0)) {
3288         php_error_docref(NULL, E_WARNING,
3289             "Warning:  Invalid MAXLEN argument or approximate flag");
3290     }
3291 
3292 
3293     /* Calculate argc for XADD.  It's a bit complex because we've got
3294      * an optional MAXLEN argument which can either take the form MAXLEN N
3295      * or MAXLEN ~ N */
3296     argc = 2 + (fcount*2) + (maxlen > 0 ? (approx ? 3 : 2) : 0);
3297 
3298     /* XADD key ID field string [field string ...] */
3299     REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "XADD");
3300     redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
3301 
3302     /* Now append our MAXLEN bits if we've got them */
3303     if (maxlen > 0) {
3304         REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "MAXLEN");
3305         REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, approx, "~");
3306         redis_cmd_append_sstr_long(&cmdstr, maxlen);
3307     }
3308 
3309     /* Now append ID and field(s) */
3310     redis_cmd_append_sstr(&cmdstr, id, idlen);
3311     ZEND_HASH_FOREACH_KEY_VAL(ht_fields, idx, arrkey, value) {
3312         redis_cmd_append_sstr_arrkey(&cmdstr, arrkey, idx);
3313         redis_cmd_append_sstr_zval(&cmdstr, value, redis_sock);
3314     } ZEND_HASH_FOREACH_END();
3315 
3316     *cmd = cmdstr.c;
3317     *cmd_len = cmdstr.len;
3318     return SUCCESS;
3319 }
3320 
3321 // XPENDING key group [start end count] [consumer]
redis_xpending_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3322 int redis_xpending_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3323                        char **cmd, int *cmd_len, short *slot, void **ctx)
3324 {
3325     smart_string cmdstr = {0};
3326     char *key, *group, *start = NULL, *end = NULL, *consumer = NULL;
3327     size_t keylen, grouplen, startlen, endlen, consumerlen;
3328     int argc;
3329     zend_long count = -1;
3330 
3331     // XPENDING mystream group55 - + 10 consumer-123
3332     if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|ssls", &key,
3333                               &keylen, &group, &grouplen, &start, &startlen,
3334                               &end, &endlen, &count, &consumer, &consumerlen)
3335                               == FAILURE)
3336     {
3337         return FAILURE;
3338     }
3339 
3340     /* If we've been passed a start argument, we also need end and count */
3341     if (start != NULL && (end == NULL || count < 0)) {
3342         return FAILURE;
3343     }
3344 
3345     /* Calculate argc.  It's either 2, 5, or 6 */
3346     argc = 2 + (start != NULL ? 3 + (consumer != NULL) : 0);
3347 
3348     /* Construct command and add required arguments */
3349     REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "XPENDING");
3350     redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
3351     redis_cmd_append_sstr(&cmdstr, group, grouplen);
3352 
3353     /* Add optional argumentst */
3354     if (start) {
3355         redis_cmd_append_sstr(&cmdstr, start, startlen);
3356         redis_cmd_append_sstr(&cmdstr, end, endlen);
3357         redis_cmd_append_sstr_long(&cmdstr, (long)count);
3358 
3359         /* Finally add consumer if we have it */
3360         if (consumer) redis_cmd_append_sstr(&cmdstr, consumer, consumerlen);
3361     }
3362 
3363     *cmd = cmdstr.c;
3364     *cmd_len = cmdstr.len;
3365     return SUCCESS;
3366 }
3367 
3368 /* X[REV]RANGE key start end [COUNT count] */
redis_xrange_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)3369 int redis_xrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3370                      char *kw, char **cmd, int *cmd_len, short *slot,
3371                      void **ctx)
3372 {
3373     smart_string cmdstr = {0};
3374     char *key, *start, *end;
3375     size_t keylen, startlen, endlen;
3376     zend_long count = -1;
3377 
3378     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|l", &key, &keylen,
3379                               &start, &startlen, &end, &endlen, &count)
3380                               == FAILURE)
3381     {
3382         return FAILURE;
3383     }
3384 
3385     redis_cmd_init_sstr(&cmdstr, 3 + (2 * (count > -1)), kw, strlen(kw));
3386     redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
3387     redis_cmd_append_sstr(&cmdstr, start, startlen);
3388     redis_cmd_append_sstr(&cmdstr, end, endlen);
3389 
3390     if (count > -1) {
3391         redis_cmd_append_sstr(&cmdstr, "COUNT", sizeof("COUNT")-1);
3392         redis_cmd_append_sstr_long(&cmdstr, count);
3393     }
3394 
3395     *cmd = cmdstr.c;
3396     *cmd_len = cmdstr.len;
3397     return SUCCESS;
3398 }
3399 
3400 /* Helper function to take an associative array and append the Redis
3401  * STREAMS stream [stream...] id [id ...] arguments to a command string. */
3402 static int
append_stream_args(smart_string * cmdstr,HashTable * ht,RedisSock * redis_sock,short * slot)3403 append_stream_args(smart_string *cmdstr, HashTable *ht, RedisSock *redis_sock,
3404                    short *slot)
3405 {
3406     char *kptr, kbuf[40];
3407     int klen, i, pos = 0;
3408     zend_string *key, *idstr;
3409     short oldslot;
3410     zval **id;
3411     zend_ulong idx;
3412 
3413     /* Append STREAM qualifier */
3414     REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "STREAMS");
3415 
3416     /* Sentinel slot */
3417     if (slot) oldslot = -1;
3418 
3419     /* Allocate memory to keep IDs */
3420     id = emalloc(sizeof(*id) * zend_hash_num_elements(ht));
3421 
3422     /* Iterate over our stream => id array appending streams and retaining each
3423      * value for final arguments */
3424     ZEND_HASH_FOREACH_KEY_VAL(ht, idx, key, id[pos++]) {
3425         if (key) {
3426             klen = ZSTR_LEN(key);
3427             kptr = ZSTR_VAL(key);
3428         } else {
3429             klen = snprintf(kbuf, sizeof(kbuf), "%ld", (long)idx);
3430             kptr = (char*)kbuf;
3431         }
3432 
3433         /* Append stream key */
3434         redis_cmd_append_sstr_key(cmdstr, kptr, klen, redis_sock, slot);
3435 
3436         /* Protect the user against CROSSSLOT to avoid confusion */
3437         if (slot) {
3438             if (oldslot != -1 && *slot != oldslot) {
3439                 php_error_docref(NULL, E_WARNING,
3440                     "Warning, not all keys hash to the same slot!");
3441                 efree(id);
3442                 return FAILURE;
3443             }
3444             oldslot = *slot;
3445         }
3446     } ZEND_HASH_FOREACH_END();
3447 
3448     /* Add our IDs */
3449     for (i = 0; i < pos; i++) {
3450         idstr = zval_get_string(id[i]);
3451         redis_cmd_append_sstr(cmdstr, ZSTR_VAL(idstr), ZSTR_LEN(idstr));
3452         zend_string_release(idstr);
3453     }
3454 
3455     /* Clean up ID container array */
3456     efree(id);
3457 
3458     return 0;
3459 }
3460 
3461 /* XREAD [COUNT count] [BLOCK ms] STREAMS key [key ...] id [id ...] */
redis_xread_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3462 int redis_xread_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3463                     char **cmd, int *cmd_len, short *slot, void **ctx)
3464 {
3465     smart_string cmdstr = {0};
3466     zend_long count = -1, block = -1;
3467     zval *z_streams;
3468     int argc, scount;
3469     HashTable *kt;
3470 
3471     if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|ll", &z_streams,
3472                               &count, &block) == FAILURE)
3473     {
3474         return FAILURE;
3475     }
3476 
3477     /* At least one stream and ID is required */
3478     kt = Z_ARRVAL_P(z_streams);
3479     if ((scount = zend_hash_num_elements(kt)) < 1) {
3480         return FAILURE;
3481     }
3482 
3483     /* Calculate argc and start constructing command */
3484     argc = 1 + (2 * scount) + (2 * (count > -1)) + (2 * (block > -1));
3485     REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "XREAD");
3486 
3487     /* Append COUNT if we have it */
3488     if (count > -1) {
3489         REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "COUNT");
3490         redis_cmd_append_sstr_long(&cmdstr, count);
3491     }
3492 
3493     /* Append BLOCK if we have it */
3494     if (block > -1) {
3495         REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "BLOCK");
3496         redis_cmd_append_sstr_long(&cmdstr, block);
3497     }
3498 
3499     /* Append final STREAM key [key ...] id [id ...] arguments */
3500     if (append_stream_args(&cmdstr, kt, redis_sock, slot) < 0) {
3501         efree(cmdstr.c);
3502         return FAILURE;
3503     }
3504 
3505     *cmd = cmdstr.c;
3506     *cmd_len = cmdstr.len;
3507     return SUCCESS;
3508 }
3509 
3510 /* XREADGROUP GROUP group consumer [COUNT count] [BLOCK ms]
3511  * STREAMS key [key ...] id [id ...] */
redis_xreadgroup_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3512 int redis_xreadgroup_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3513                          char **cmd, int *cmd_len, short *slot, void **ctx)
3514 {
3515     smart_string cmdstr = {0};
3516     zval *z_streams;
3517     HashTable *kt;
3518     char *group, *consumer;
3519     size_t grouplen, consumerlen;
3520     int scount, argc;
3521     zend_long count, block;
3522     zend_bool no_count = 1, no_block = 1;
3523 
3524     if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssa|l!l!", &group,
3525                               &grouplen, &consumer, &consumerlen, &z_streams,
3526                               &count, &no_count, &block, &no_block) == FAILURE)
3527     {
3528         return FAILURE;
3529     }
3530 
3531     /* Negative COUNT or BLOCK is illegal so abort immediately */
3532     if ((!no_count && count < 0) || (!no_block && block < 0)) {
3533         php_error_docref(NULL, E_WARNING, "Negative values for COUNT or BLOCK are illegal.");
3534         return FAILURE;
3535     }
3536 
3537     /* Redis requires at least one stream */
3538     kt = Z_ARRVAL_P(z_streams);
3539     if ((scount = zend_hash_num_elements(kt)) < 1) {
3540         return FAILURE;
3541     }
3542 
3543     /* Calculate argc and start constructing commands */
3544     argc = 4 + (2 * scount) + (2 * !no_count) + (2 * !no_block);
3545     REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "XREADGROUP");
3546 
3547     /* Group and consumer */
3548     REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "GROUP");
3549     redis_cmd_append_sstr(&cmdstr, group, grouplen);
3550     redis_cmd_append_sstr(&cmdstr, consumer, consumerlen);
3551 
3552     /* Append COUNT if we have it */
3553     if (!no_count) {
3554         REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "COUNT");
3555         redis_cmd_append_sstr_long(&cmdstr, count);
3556     }
3557 
3558     /* Append BLOCK argument if we have it */
3559     if (!no_block) {
3560         REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "BLOCK");
3561         redis_cmd_append_sstr_long(&cmdstr, block);
3562     }
3563 
3564     /* Finally append stream and id args */
3565     if (append_stream_args(&cmdstr, kt, redis_sock, slot) < 0) {
3566         efree(cmdstr.c);
3567         return FAILURE;
3568     }
3569 
3570     *cmd = cmdstr.c;
3571     *cmd_len = cmdstr.len;
3572     return SUCCESS;
3573 }
3574 
3575 /* XACK key group id [id ...] */
redis_xack_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3576 int redis_xack_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3577                    char **cmd, int *cmd_len, short *slot, void **ctx)
3578 {
3579     smart_string cmdstr = {0};
3580     char *key, *group;
3581     size_t keylen, grouplen;
3582     zend_string *idstr;
3583     zval *z_ids, *z_id;
3584     HashTable *ht_ids;
3585     int idcount;
3586 
3587     if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssa", &key, &keylen,
3588                               &group, &grouplen, &z_ids) == FAILURE)
3589     {
3590         return FAILURE;
3591     }
3592 
3593     ht_ids = Z_ARRVAL_P(z_ids);
3594     if ((idcount = zend_hash_num_elements(ht_ids)) < 1) {
3595         return FAILURE;
3596     }
3597 
3598     /* Create command and add initial arguments */
3599     REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 2 + idcount, "XACK");
3600     redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
3601     redis_cmd_append_sstr(&cmdstr, group, grouplen);
3602 
3603     /* Append IDs */
3604     ZEND_HASH_FOREACH_VAL(ht_ids, z_id) {
3605         idstr = zval_get_string(z_id);
3606         redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(idstr), ZSTR_LEN(idstr));
3607         zend_string_release(idstr);
3608     } ZEND_HASH_FOREACH_END();
3609 
3610     *cmd = cmdstr.c;
3611     *cmd_len = cmdstr.len;
3612     return SUCCESS;
3613 }
3614 
3615 /* XCLAIM options container */
3616 typedef struct xclaimOptions {
3617     struct {
3618         char *type;
3619         int64_t time;
3620     } idle;
3621     zend_long retrycount;
3622     int force;
3623     int justid;
3624 } xclaimOptions;
3625 
3626 /* Attempt to extract an int64_t from the provided zval */
zval_get_i64(zval * zv,int64_t * retval)3627 static int zval_get_i64(zval *zv, int64_t *retval) {
3628     if (Z_TYPE_P(zv) == IS_LONG) {
3629         *retval = (int64_t)Z_LVAL_P(zv);
3630         return SUCCESS;
3631     } else if (Z_TYPE_P(zv) == IS_DOUBLE) {
3632         *retval = (int64_t)Z_DVAL_P(zv);
3633         return SUCCESS;
3634     } else if (Z_TYPE_P(zv) == IS_STRING) {
3635         zend_long lval;
3636         double dval;
3637 
3638         switch (is_numeric_string(Z_STRVAL_P(zv), Z_STRLEN_P(zv), &lval, &dval, 1)) {
3639             case IS_LONG:
3640                 *retval = (int64_t)lval;
3641                 return SUCCESS;
3642             case IS_DOUBLE:
3643                 *retval = (int64_t)dval;
3644                 return SUCCESS;
3645         }
3646     }
3647 
3648     /* If we make it here we have failed */
3649     return FAILURE;
3650 }
3651 
3652 /* Helper function to get an integer XCLAIM argument.  This can overflow a
3653  * 32-bit PHP long so we have to extract it as an int64_t.  If the value is
3654  * not a valid number or negative, we'll inform the user of the problem and
3655  * that the argument is being ignored. */
get_xclaim_i64_arg(const char * key,zval * zv)3656 static int64_t get_xclaim_i64_arg(const char *key, zval *zv) {
3657     int64_t retval = -1;
3658 
3659     /* Extract an i64, and if we can't let the user know there is an issue. */
3660     if (zval_get_i64(zv, &retval) == FAILURE || retval < 0) {
3661         php_error_docref(NULL, E_WARNING,
3662             "Invalid XCLAIM option '%s' will be ignored", key);
3663     }
3664 
3665     return retval;
3666 }
3667 
3668 /* Helper to extract XCLAIM options */
get_xclaim_options(zval * z_arr,xclaimOptions * opt)3669 static void get_xclaim_options(zval *z_arr, xclaimOptions *opt) {
3670     HashTable *ht;
3671     zend_string *zkey;
3672     char *kval;
3673     size_t klen;
3674     zval *zv;
3675 
3676     /* Initialize options array to sane defaults */
3677     memset(opt, 0, sizeof(*opt));
3678     opt->retrycount = -1;
3679     opt->idle.time = -1;
3680 
3681     /* Early return if we don't have any options */
3682     if (z_arr == NULL)
3683         return;
3684 
3685     /* Iterate over our options array */
3686     ht = Z_ARRVAL_P(z_arr);
3687     ZEND_HASH_FOREACH_STR_KEY_VAL(ht, zkey, zv) {
3688         if (zkey) {
3689             kval = ZSTR_VAL(zkey);
3690             klen = ZSTR_LEN(zkey);
3691 
3692             if (klen == 4) {
3693                 if (!strncasecmp(kval, "TIME", 4)) {
3694                     opt->idle.type = "TIME";
3695                     opt->idle.time = get_xclaim_i64_arg("TIME", zv);
3696                 } else if (!strncasecmp(kval, "IDLE", 4)) {
3697                     opt->idle.type = "IDLE";
3698                     opt->idle.time = get_xclaim_i64_arg("IDLE", zv);
3699                 }
3700             } else if (klen == 10 && !strncasecmp(kval, "RETRYCOUNT", 10)) {
3701                 opt->retrycount = zval_get_long(zv);
3702             }
3703         } else {
3704             if (Z_TYPE_P(zv) == IS_STRING) {
3705                 kval = Z_STRVAL_P(zv);
3706                 klen = Z_STRLEN_P(zv);
3707                 if (klen == 5 && !strncasecmp(kval, "FORCE", 5)) {
3708                     opt->force = 1;
3709                 } else if (klen == 6 && !strncasecmp(kval, "JUSTID", 6)) {
3710                     opt->justid = 1;
3711                 }
3712             }
3713         }
3714     } ZEND_HASH_FOREACH_END();
3715 }
3716 
3717 /* Count argc for any options we may have */
xclaim_options_argc(xclaimOptions * opt)3718 static int xclaim_options_argc(xclaimOptions *opt) {
3719     int argc = 0;
3720 
3721     if (opt->idle.type != NULL && opt->idle.time != -1)
3722         argc += 2;
3723     if (opt->retrycount != -1)
3724         argc += 2;
3725     if (opt->force)
3726         argc++;
3727     if (opt->justid)
3728         argc++;
3729 
3730     return argc;
3731 }
3732 
3733 /* Append XCLAIM options */
append_xclaim_options(smart_string * cmd,xclaimOptions * opt)3734 static void append_xclaim_options(smart_string *cmd, xclaimOptions *opt) {
3735     /* IDLE/TIME long */
3736     if (opt->idle.type != NULL && opt->idle.time != -1) {
3737         redis_cmd_append_sstr(cmd, opt->idle.type, strlen(opt->idle.type));
3738         redis_cmd_append_sstr_i64(cmd, opt->idle.time);
3739     }
3740 
3741     /* RETRYCOUNT */
3742     if (opt->retrycount != -1) {
3743         REDIS_CMD_APPEND_SSTR_STATIC(cmd, "RETRYCOUNT");
3744         redis_cmd_append_sstr_long(cmd, opt->retrycount);
3745     }
3746 
3747     /* FORCE and JUSTID */
3748     if (opt->force)
3749         REDIS_CMD_APPEND_SSTR_STATIC(cmd, "FORCE");
3750     if (opt->justid)
3751         REDIS_CMD_APPEND_SSTR_STATIC(cmd, "JUSTID");
3752 }
3753 
3754 /* XCLAIM <key> <group> <consumer> <min-idle-time> <ID-1> <ID-2>
3755           [IDLE <milliseconds>] [TIME <mstime>] [RETRYCOUNT <count>]
3756           [FORCE] [JUSTID] */
redis_xclaim_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3757 int redis_xclaim_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3758 					 char **cmd, int *cmd_len, short *slot, void **ctx)
3759 {
3760     smart_string cmdstr = {0};
3761     char *key, *group, *consumer;
3762     size_t keylen, grouplen, consumerlen;
3763     zend_long min_idle;
3764     int argc, id_count;
3765     zval *z_ids, *z_id, *z_opts = NULL;
3766     zend_string *zstr;
3767     HashTable *ht_ids;
3768     xclaimOptions opts;
3769 
3770     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sssla|a", &key, &keylen,
3771                               &group, &grouplen, &consumer, &consumerlen, &min_idle,
3772                               &z_ids, &z_opts) == FAILURE)
3773     {
3774         return FAILURE;
3775     }
3776 
3777     /* At least one id is required */
3778     ht_ids = Z_ARRVAL_P(z_ids);
3779     if ((id_count = zend_hash_num_elements(ht_ids)) < 1) {
3780         return FAILURE;
3781     }
3782 
3783     /* Extract options array if we've got them */
3784     get_xclaim_options(z_opts, &opts);
3785 
3786     /* Now we have enough information to calculate argc */
3787     argc = 4 + id_count + xclaim_options_argc(&opts);
3788 
3789     /* Start constructing our command */
3790     REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "XCLAIM");
3791     redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
3792     redis_cmd_append_sstr(&cmdstr, group, grouplen);
3793     redis_cmd_append_sstr(&cmdstr, consumer, consumerlen);
3794     redis_cmd_append_sstr_long(&cmdstr, min_idle);
3795 
3796     /* Add IDs */
3797     ZEND_HASH_FOREACH_VAL(ht_ids, z_id) {
3798         zstr = zval_get_string(z_id);
3799         redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
3800         zend_string_release(zstr);
3801     } ZEND_HASH_FOREACH_END();
3802 
3803     /* Finally add our options */
3804     append_xclaim_options(&cmdstr, &opts);
3805 
3806     /* Success */
3807     *cmd = cmdstr.c;
3808     *cmd_len = cmdstr.len;
3809 	return SUCCESS;
3810 }
3811 
3812 /* XGROUP HELP
3813  * XGROUP CREATE key groupname id [MKSTREAM]
3814  * XGROUP SETID key group id
3815  * XGROUP DESTROY key groupname
3816  * XGROUP DELCONSUMER key groupname consumername */
redis_xgroup_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3817 int redis_xgroup_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3818                      char **cmd, int *cmd_len, short *slot, void **ctx)
3819 {
3820     char *op, *key = NULL, *arg1 = NULL, *arg2 = NULL;
3821     size_t oplen, keylen, arg1len, arg2len;
3822     zend_bool mkstream = 0;
3823     int argc = ZEND_NUM_ARGS();
3824 
3825     if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|sssb", &op, &oplen,
3826                               &key, &keylen, &arg1, &arg1len, &arg2, &arg2len,
3827                               &mkstream) == FAILURE)
3828     {
3829         return FAILURE;
3830     }
3831 
3832     if (argc == 1 && oplen == 4 && !strncasecmp(op, "HELP", 4)) {
3833         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "s", "HELP", 4);
3834         return SUCCESS;
3835     } else if (argc >= 4 && (oplen == 6 && !strncasecmp(op, "CREATE", 6))) {
3836         if (mkstream) {
3837             *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "sksss", op, oplen, key, keylen,
3838                                           arg1, arg1len, arg2, arg2len, "MKSTREAM",
3839                                           sizeof("MKSTREAM") - 1);
3840         } else {
3841             *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "skss", op, oplen, key, keylen,
3842                                           arg1, arg1len, arg2, arg2len);
3843         }
3844         return SUCCESS;
3845     } else if (argc == 4 && ((oplen == 5 && !strncasecmp(op, "SETID", 5)) ||
3846                              (oplen == 11 && !strncasecmp(op, "DELCONSUMER", 11))))
3847     {
3848         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "skss", op, oplen, key, keylen,
3849                                       arg1, arg1len, arg2, arg2len);
3850         return SUCCESS;
3851     } else if (argc == 3 && ((oplen == 7 && !strncasecmp(op, "DESTROY", 7)))) {
3852         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "sks", op, oplen, key,
3853                                       keylen, arg1, arg1len);
3854         return SUCCESS;
3855     }
3856 
3857     /* Didn't detect any valid XGROUP command pattern */
3858     return FAILURE;
3859 }
3860 
3861 /* XINFO CONSUMERS key group
3862  * XINFO GROUPS key
3863  * XINFO STREAM key [FULL [COUNT N]]
3864  * XINFO HELP */
redis_xinfo_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3865 int redis_xinfo_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3866                      char **cmd, int *cmd_len, short *slot, void **ctx)
3867 {
3868     char *op, *key, *arg = NULL;
3869     size_t oplen, keylen, arglen;
3870     zend_long count = -1;
3871     int argc = ZEND_NUM_ARGS();
3872     char fmt[] = "skssl";
3873 
3874     if (argc > 4 || zend_parse_parameters(ZEND_NUM_ARGS(), "s|ssl",
3875                                           &op, &oplen, &key, &keylen, &arg,
3876                                           &arglen, &count) == FAILURE)
3877     {
3878         return FAILURE;
3879     }
3880 
3881     /* Handle everything except XINFO STREAM */
3882     if (strncasecmp(op, "STREAM", 6) != 0) {
3883         fmt[argc] = '\0';
3884         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XINFO", fmt, op, oplen, key, keylen,
3885                                       arg, arglen);
3886         return SUCCESS;
3887     }
3888 
3889     /* 'FULL' is the only legal option to XINFO STREAM */
3890     if (argc > 2 && strncasecmp(arg, "FULL", 4) != 0) {
3891         php_error_docref(NULL, E_WARNING, "'%s' is not a valid option for XINFO STREAM", arg);
3892         return FAILURE;
3893     }
3894 
3895     /* If we have a COUNT bump the argument count to account for the 'COUNT' literal */
3896     if (argc == 4) argc++;
3897 
3898     fmt[argc] = '\0';
3899 
3900     /* Build our XINFO STREAM variant */
3901     *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XINFO", fmt, "STREAM", 6, key, keylen,
3902                                   "FULL", 4, "COUNT", 5, count);
3903 
3904     return SUCCESS;
3905 }
3906 
3907 /* XTRIM MAXLEN [~] count */
redis_xtrim_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char ** cmd,int * cmd_len,short * slot,void ** ctx)3908 int redis_xtrim_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3909                     char **cmd, int *cmd_len, short *slot, void **ctx)
3910 {
3911     char *key;
3912     size_t keylen;
3913     zend_long maxlen;
3914     zend_bool approx = 0;
3915 
3916     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl|b", &key, &keylen,
3917                               &maxlen, &approx) == FAILURE)
3918     {
3919         return FAILURE;
3920     }
3921 
3922     if (approx) {
3923         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XTRIM", "kssl", key, keylen,
3924                                       "MAXLEN", 6, "~", 1, maxlen);
3925     } else {
3926         *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XTRIM", "ksl", key, keylen,
3927                                       "MAXLEN", 6, maxlen);
3928     }
3929 
3930     return SUCCESS;
3931 }
3932 
3933 int
redis_sentinel_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)3934 redis_sentinel_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3935                     char *kw, char **cmd, int *cmd_len, short *slot, void **ctx)
3936 {
3937     if (zend_parse_parameters_none() == FAILURE) {
3938 
3939         return FAILURE;
3940     }
3941     *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SENTINEL", "s", kw, strlen(kw));
3942     return SUCCESS;
3943 }
3944 
3945 int
redis_sentinel_str_cmd(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,char * kw,char ** cmd,int * cmd_len,short * slot,void ** ctx)3946 redis_sentinel_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3947                     char *kw, char **cmd, int *cmd_len, short *slot, void **ctx)
3948 {
3949     zend_string *name;
3950 
3951     if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &name) == FAILURE) {
3952         return FAILURE;
3953     }
3954     *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SENTINEL", "sS", kw, strlen(kw), name);
3955     return SUCCESS;
3956 }
3957 
3958 /*
3959  * Redis commands that don't deal with the server at all.  The RedisSock*
3960  * pointer is the only thing retrieved differently, so we just take that
3961  * in addition to the standard INTERNAL_FUNCTION_PARAMETERS for arg parsing,
3962  * return value handling, and thread safety. */
3963 
redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,redisCluster * c)3964 void redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS,
3965                              RedisSock *redis_sock, redisCluster *c)
3966 {
3967     zend_long option;
3968 
3969     if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &option)
3970                               == FAILURE)
3971     {
3972         RETURN_FALSE;
3973     }
3974 
3975     // Return the requested option
3976     switch(option) {
3977         case REDIS_OPT_SERIALIZER:
3978             RETURN_LONG(redis_sock->serializer);
3979         case REDIS_OPT_COMPRESSION:
3980             RETURN_LONG(redis_sock->compression);
3981         case REDIS_OPT_COMPRESSION_LEVEL:
3982             RETURN_LONG(redis_sock->compression_level);
3983         case REDIS_OPT_PREFIX:
3984             if (redis_sock->prefix) {
3985                 RETURN_STRINGL(ZSTR_VAL(redis_sock->prefix), ZSTR_LEN(redis_sock->prefix));
3986             }
3987             RETURN_NULL();
3988         case REDIS_OPT_READ_TIMEOUT:
3989             RETURN_DOUBLE(redis_sock->read_timeout);
3990         case REDIS_OPT_TCP_KEEPALIVE:
3991             RETURN_LONG(redis_sock->tcp_keepalive);
3992         case REDIS_OPT_SCAN:
3993             RETURN_LONG(redis_sock->scan);
3994         case REDIS_OPT_REPLY_LITERAL:
3995             RETURN_LONG(redis_sock->reply_literal);
3996         case REDIS_OPT_NULL_MBULK_AS_NULL:
3997             RETURN_LONG(redis_sock->null_mbulk_as_null);
3998         case REDIS_OPT_FAILOVER:
3999             RETURN_LONG(c->failover);
4000         case REDIS_OPT_MAX_RETRIES:
4001             RETURN_LONG(redis_sock->max_retries);
4002         case REDIS_OPT_BACKOFF_ALGORITHM:
4003             RETURN_LONG(redis_sock->backoff.algorithm);
4004         case REDIS_OPT_BACKOFF_BASE:
4005             RETURN_LONG(redis_sock->backoff.base / 1000);
4006         case REDIS_OPT_BACKOFF_CAP:
4007             RETURN_LONG(redis_sock->backoff.cap / 1000);
4008         default:
4009             RETURN_FALSE;
4010     }
4011 }
4012 
redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,redisCluster * c)4013 void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS,
4014                              RedisSock *redis_sock, redisCluster *c)
4015 {
4016     zend_long val_long, option;
4017     zval *val;
4018     zend_string *val_str;
4019     struct timeval read_tv;
4020     int tcp_keepalive = 0;
4021     php_netstream_data_t *sock;
4022 
4023     if (zend_parse_parameters(ZEND_NUM_ARGS(), "lz", &option,
4024                               &val) == FAILURE)
4025     {
4026         RETURN_FALSE;
4027     }
4028 
4029     switch(option) {
4030         case REDIS_OPT_SERIALIZER:
4031             val_long = zval_get_long(val);
4032             if (val_long == REDIS_SERIALIZER_NONE
4033                 || val_long == REDIS_SERIALIZER_PHP
4034                 || val_long == REDIS_SERIALIZER_JSON
4035 #ifdef HAVE_REDIS_IGBINARY
4036                 || val_long == REDIS_SERIALIZER_IGBINARY
4037 #endif
4038 #ifdef HAVE_REDIS_MSGPACK
4039                 || val_long == REDIS_SERIALIZER_MSGPACK
4040 #endif
4041             ) {
4042                 redis_sock->serializer = val_long;
4043                 RETURN_TRUE;
4044             }
4045             break;
4046         case REDIS_OPT_REPLY_LITERAL:
4047             val_long = zval_get_long(val);
4048             redis_sock->reply_literal = val_long != 0;
4049             RETURN_TRUE;
4050         case REDIS_OPT_NULL_MBULK_AS_NULL:
4051             val_long = zval_get_long(val);
4052             redis_sock->null_mbulk_as_null = val_long != 0;
4053             RETURN_TRUE;
4054         case REDIS_OPT_COMPRESSION:
4055             val_long = zval_get_long(val);
4056             if (val_long == REDIS_COMPRESSION_NONE
4057 #ifdef HAVE_REDIS_LZF
4058                 || val_long == REDIS_COMPRESSION_LZF
4059 #endif
4060 #ifdef HAVE_REDIS_ZSTD
4061                 || val_long == REDIS_COMPRESSION_ZSTD
4062 #endif
4063 #ifdef HAVE_REDIS_LZ4
4064                 || val_long == REDIS_COMPRESSION_LZ4
4065 #endif
4066             ) {
4067                 redis_sock->compression = val_long;
4068                 RETURN_TRUE;
4069             }
4070             break;
4071         case REDIS_OPT_COMPRESSION_LEVEL:
4072             val_long = zval_get_long(val);
4073             redis_sock->compression_level = val_long;
4074             RETURN_TRUE;
4075         case REDIS_OPT_PREFIX:
4076             if (redis_sock->prefix) {
4077                 zend_string_release(redis_sock->prefix);
4078                 redis_sock->prefix = NULL;
4079             }
4080             val_str = zval_get_string(val);
4081             if (ZSTR_LEN(val_str) > 0) {
4082                 redis_sock->prefix = val_str;
4083             } else {
4084                 zend_string_release(val_str);
4085             }
4086             RETURN_TRUE;
4087         case REDIS_OPT_READ_TIMEOUT:
4088             redis_sock->read_timeout = zval_get_double(val);
4089             if (redis_sock->stream) {
4090                 read_tv.tv_sec  = (time_t)redis_sock->read_timeout;
4091                 read_tv.tv_usec = (int)((redis_sock->read_timeout -
4092                                          read_tv.tv_sec) * 1000000);
4093                 php_stream_set_option(redis_sock->stream,
4094                                       PHP_STREAM_OPTION_READ_TIMEOUT, 0,
4095                                       &read_tv);
4096             }
4097             RETURN_TRUE;
4098         case REDIS_OPT_TCP_KEEPALIVE:
4099 
4100             /* Don't set TCP_KEEPALIVE if we're using a unix socket. */
4101             if (ZSTR_VAL(redis_sock->host)[0] == '/' && redis_sock->port < 1) {
4102                 RETURN_FALSE;
4103             }
4104             tcp_keepalive = zval_get_long(val) > 0 ? 1 : 0;
4105             if (redis_sock->tcp_keepalive == tcp_keepalive) {
4106                 RETURN_TRUE;
4107             }
4108             if (redis_sock->stream) {
4109                 /* set TCP_KEEPALIVE */
4110                 sock = (php_netstream_data_t*)redis_sock->stream->abstract;
4111                 if (setsockopt(sock->socket, SOL_SOCKET, SO_KEEPALIVE, (char*)&tcp_keepalive,
4112                             sizeof(tcp_keepalive)) == -1) {
4113                     RETURN_FALSE;
4114                 }
4115                 redis_sock->tcp_keepalive = tcp_keepalive;
4116             }
4117             RETURN_TRUE;
4118         case REDIS_OPT_SCAN:
4119             val_long = zval_get_long(val);
4120             if (val_long == REDIS_SCAN_NORETRY) {
4121                 redis_sock->scan &= ~REDIS_SCAN_RETRY;
4122             } else if (val_long == REDIS_SCAN_NOPREFIX) {
4123                 redis_sock->scan &= ~REDIS_SCAN_PREFIX;
4124             } else if (val_long == REDIS_SCAN_RETRY || val_long == REDIS_SCAN_PREFIX) {
4125                 redis_sock->scan |= val_long;
4126             } else {
4127                 break;
4128             }
4129             RETURN_TRUE;
4130         case REDIS_OPT_FAILOVER:
4131             if (c == NULL) RETURN_FALSE;
4132             val_long = zval_get_long(val);
4133             if (val_long == REDIS_FAILOVER_NONE ||
4134                 val_long == REDIS_FAILOVER_ERROR ||
4135                 val_long == REDIS_FAILOVER_DISTRIBUTE ||
4136                 val_long == REDIS_FAILOVER_DISTRIBUTE_SLAVES)
4137             {
4138                 c->failover = val_long;
4139                 RETURN_TRUE;
4140             }
4141             break;
4142         case REDIS_OPT_MAX_RETRIES:
4143             val_long = zval_get_long(val);
4144             if(val_long >= 0) {
4145                 redis_sock->max_retries = val_long;
4146                 RETURN_TRUE;
4147             }
4148             break;
4149         case REDIS_OPT_BACKOFF_ALGORITHM:
4150             val_long = zval_get_long(val);
4151             if(val_long >= 0 &&
4152                val_long < REDIS_BACKOFF_ALGORITHMS) {
4153                 redis_sock->backoff.algorithm = val_long;
4154                 RETURN_TRUE;
4155             }
4156             break;
4157         case REDIS_OPT_BACKOFF_BASE:
4158             val_long = zval_get_long(val);
4159             if(val_long >= 0) {
4160                 redis_sock->backoff.base = val_long * 1000;
4161                 RETURN_TRUE;
4162             }
4163             break;
4164         case REDIS_OPT_BACKOFF_CAP:
4165             val_long = zval_get_long(val);
4166             if(val_long >= 0) {
4167                 redis_sock->backoff.cap = val_long * 1000;
4168                 RETURN_TRUE;
4169             }
4170             break;
4171         EMPTY_SWITCH_DEFAULT_CASE()
4172     }
4173     RETURN_FALSE;
4174 }
4175 
redis_prefix_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock)4176 void redis_prefix_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
4177     char *key;
4178     size_t key_len;
4179 
4180     if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &key, &key_len)
4181                              ==FAILURE)
4182     {
4183         RETURN_FALSE;
4184     }
4185 
4186     if (redis_sock->prefix) {
4187         int keyfree = redis_key_prefix(redis_sock, &key, &key_len);
4188         RETVAL_STRINGL(key, key_len);
4189         if (keyfree) efree(key);
4190     } else {
4191         RETURN_STRINGL(key, key_len);
4192     }
4193 }
4194 
redis_serialize_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock)4195 void redis_serialize_handler(INTERNAL_FUNCTION_PARAMETERS,
4196                              RedisSock *redis_sock)
4197 {
4198     zval *z_val;
4199     char *val;
4200     size_t val_len;
4201 
4202     if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &z_val) == FAILURE) {
4203         RETURN_FALSE;
4204     }
4205 
4206     int val_free = redis_serialize(redis_sock, z_val, &val, &val_len);
4207 
4208     RETVAL_STRINGL(val, val_len);
4209     if (val_free) efree(val);
4210 }
4211 
redis_unserialize_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zend_class_entry * ex)4212 void redis_unserialize_handler(INTERNAL_FUNCTION_PARAMETERS,
4213                                RedisSock *redis_sock, zend_class_entry *ex)
4214 {
4215     char *value;
4216     size_t value_len;
4217 
4218     // Parse our arguments
4219     if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &value, &value_len)
4220                                     == FAILURE)
4221     {
4222         RETURN_FALSE;
4223     }
4224 
4225     // We only need to attempt unserialization if we have a serializer running
4226     if (redis_sock->serializer == REDIS_SERIALIZER_NONE) {
4227         // Just return the value that was passed to us
4228         RETURN_STRINGL(value, value_len);
4229     }
4230 
4231     zval z_ret;
4232     if (!redis_unserialize(redis_sock, value, value_len, &z_ret)) {
4233         // Badly formed input, throw an exception
4234         zend_throw_exception(ex, "Invalid serialized data, or unserialization error", 0);
4235         RETURN_FALSE;
4236     }
4237     RETURN_ZVAL(&z_ret, 0, 0);
4238 }
4239 
redis_compress_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock)4240 void redis_compress_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
4241     zend_string *zstr;
4242     size_t len;
4243     char *buf;
4244     int cmp_free;
4245 
4246     if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &zstr) == FAILURE) {
4247         RETURN_FALSE;
4248     }
4249 
4250     cmp_free = redis_compress(redis_sock, &buf, &len, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
4251     RETVAL_STRINGL(buf, len);
4252     if (cmp_free) efree(buf);
4253 }
4254 
redis_uncompress_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zend_class_entry * ex)4255 void redis_uncompress_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
4256                               zend_class_entry *ex)
4257 {
4258     zend_string *zstr;
4259     size_t len;
4260     char *buf;
4261 
4262     if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &zstr) == FAILURE) {
4263         RETURN_FALSE;
4264     } else if (ZSTR_LEN(zstr) == 0 || redis_sock->compression == REDIS_COMPRESSION_NONE) {
4265         RETURN_STR_COPY(zstr);
4266     }
4267 
4268     if (!redis_uncompress(redis_sock, &buf, &len, ZSTR_VAL(zstr), ZSTR_LEN(zstr))) {
4269         zend_throw_exception(ex, "Invalid compressed data or uncompression error", 0);
4270         RETURN_FALSE;
4271     }
4272 
4273     RETVAL_STRINGL(buf, len);
4274     efree(buf);
4275 }
4276 
redis_pack_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock)4277 void redis_pack_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
4278     int valfree;
4279     size_t len;
4280     char *val;
4281     zval *zv;
4282 
4283     if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zv) == FAILURE) {
4284         RETURN_FALSE;
4285     }
4286 
4287     valfree = redis_pack(redis_sock, zv, &val, &len);
4288     RETVAL_STRINGL(val, len);
4289     if (valfree) efree(val);
4290 }
4291 
redis_unpack_handler(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock)4292 void redis_unpack_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
4293     zend_string *str;
4294 
4295     if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) {
4296         RETURN_FALSE;
4297     }
4298 
4299     if (redis_unpack(redis_sock, ZSTR_VAL(str), ZSTR_LEN(str), return_value) == 0) {
4300         RETURN_STR_COPY(str);
4301     }
4302 }
4303 /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */
4304