1 #include "php_redis.h"
2 
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
6 
7 #include "common.h"
8 #include "php_network.h"
9 #include <sys/types.h>
10 
11 #ifdef HAVE_REDIS_IGBINARY
12 #include "igbinary/igbinary.h"
13 #endif
14 #ifdef HAVE_REDIS_MSGPACK
15 #include "msgpack/php_msgpack.h"
16 #endif
17 
18 #ifdef HAVE_REDIS_LZF
19 #include <lzf.h>
20 
21     #ifndef LZF_MARGIN
22         #define LZF_MARGIN 128
23     #endif
24 #endif
25 
26 #ifdef HAVE_REDIS_ZSTD
27 #include <zstd.h>
28 #endif
29 
30 #ifdef HAVE_REDIS_LZ4
31 #include <lz4.h>
32 #include <lz4hc.h>
33 
34 /* uint8_t crf + int length */
35 #define REDIS_LZ4_HDR_SIZE (sizeof(uint8_t) + sizeof(int))
36 #if defined(LZ4HC_CLEVEL_MAX)
37 /* version >= 1.7.5 */
38 #define REDIS_LZ4_MAX_CLEVEL LZ4HC_CLEVEL_MAX
39 
40 #elif defined (LZ4HC_MAX_CLEVEL)
41 /* version >= 1.7.3 */
42 #define REDIS_LZ4_MAX_CLEVEL LZ4HC_MAX_CLEVEL
43 
44 #else
45 /* older versions */
46 #define REDIS_LZ4_MAX_CLEVEL 12
47 #endif
48 #endif
49 
50 #include <zend_exceptions.h>
51 #include "php_redis.h"
52 #include "library.h"
53 #include "redis_commands.h"
54 
55 #ifdef HAVE_REDIS_JSON
56 #include <ext/json/php_json.h>
57 #endif
58 
59 #include <ext/standard/php_rand.h>
60 #include <ext/hash/php_hash.h>
61 
62 #define UNSERIALIZE_NONE 0
63 #define UNSERIALIZE_KEYS 1
64 #define UNSERIALIZE_VALS 2
65 #define UNSERIALIZE_ALL  3
66 
67 #define SCORE_DECODE_NONE 0
68 #define SCORE_DECODE_INT  1
69 #define SCORE_DECODE_DOUBLE 2
70 
71 #ifndef PHP_WIN32
72     #include <netinet/tcp.h> /* TCP_NODELAY */
73     #include <sys/socket.h>  /* SO_KEEPALIVE */
74 #else
75     #include <winsock.h>
76 #endif
77 
78 extern zend_class_entry *redis_ce;
79 extern zend_class_entry *redis_exception_ce;
80 
81 extern int le_redis_pconnect;
82 
83 static int redis_mbulk_reply_zipped_raw_variant(RedisSock *redis_sock, zval *zret, int count);
84 
85 /* Register a persistent resource in a a way that works for every PHP 7 version. */
redis_register_persistent_resource(zend_string * id,void * ptr,int le_id)86 void redis_register_persistent_resource(zend_string *id, void *ptr, int le_id) {
87 #if PHP_VERSION_ID < 70300
88     zend_resource res;
89     res.type = le_id;
90     res.ptr = ptr;
91 
92     zend_hash_str_update_mem(&EG(persistent_list), ZSTR_VAL(id), ZSTR_LEN(id), &res, sizeof(res));
93 #else
94     zend_register_persistent_resource(ZSTR_VAL(id), ZSTR_LEN(id), ptr, le_id);
95 #endif
96 }
97 
98 static ConnectionPool *
redis_sock_get_connection_pool(RedisSock * redis_sock)99 redis_sock_get_connection_pool(RedisSock *redis_sock)
100 {
101     ConnectionPool *pool;
102     zend_resource *le;
103     zend_string *persistent_id;
104 
105     /* Generate our unique pool id depending on configuration */
106     persistent_id = redis_pool_spprintf(redis_sock, INI_STR("redis.pconnect.pool_pattern"));
107 
108     /* Return early if we can find the pool */
109     if ((le = zend_hash_find_ptr(&EG(persistent_list), persistent_id))) {
110         zend_string_release(persistent_id);
111         return le->ptr;
112     }
113 
114     /* Create the pool and store it in our persistent list */
115     pool = pecalloc(1, sizeof(*pool), 1);
116     zend_llist_init(&pool->list, sizeof(php_stream *), NULL, 1);
117     redis_register_persistent_resource(persistent_id, pool, le_redis_pconnect);
118 
119     zend_string_release(persistent_id);
120     return pool;
121 }
122 
123 /* Helper to reselect the proper DB number when we reconnect */
reselect_db(RedisSock * redis_sock)124 static int reselect_db(RedisSock *redis_sock) {
125     char *cmd, *response;
126     int cmd_len, response_len;
127 
128     cmd_len = redis_spprintf(redis_sock, NULL, &cmd, "SELECT", "d",
129                              redis_sock->dbNumber);
130 
131     if (redis_sock_write(redis_sock, cmd, cmd_len) < 0) {
132         efree(cmd);
133         return -1;
134     }
135 
136     efree(cmd);
137 
138     if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) {
139         return -1;
140     }
141 
142     if (strncmp(response, "+OK", 3)) {
143         efree(response);
144         return -1;
145     }
146 
147     efree(response);
148     return 0;
149 }
150 
151 /* Attempt to read a single +OK response */
redis_sock_read_ok(RedisSock * redis_sock)152 static int redis_sock_read_ok(RedisSock *redis_sock) {
153     char buf[64];
154     size_t len;
155 
156     if (redis_sock_read_single_line(redis_sock, buf, sizeof(buf), &len, 0) < 0)
157         return FAILURE;
158 
159     return REDIS_STRCMP_STATIC(buf, len, "OK") ? SUCCESS : FAILURE;
160 }
161 
162 /* Append an AUTH command to a smart string if neccessary.  This will either
163  * append the new style AUTH <user> <password>, old style AUTH <password>, or
164  * append no command at all.  Function returns 1 if we appended a command
165  * and 0 otherwise. */
redis_sock_append_auth(RedisSock * redis_sock,smart_string * str)166 static int redis_sock_append_auth(RedisSock *redis_sock, smart_string *str) {
167     /* We need a password at least */
168     if (redis_sock->pass == NULL)
169         return 0;
170 
171     REDIS_CMD_INIT_SSTR_STATIC(str, !!redis_sock->user + !!redis_sock->pass, "AUTH");
172 
173     if (redis_sock->user)
174         redis_cmd_append_sstr_zstr(str, redis_sock->user);
175 
176     redis_cmd_append_sstr_zstr(str, redis_sock->pass);
177 
178     /* We appended a command */
179     return 1;
180 }
181 
182 PHP_REDIS_API void
redis_sock_copy_auth(RedisSock * dst,RedisSock * src)183 redis_sock_copy_auth(RedisSock *dst, RedisSock *src) {
184     redis_sock_set_auth(dst, src->user, src->pass);
185 }
186 
187 PHP_REDIS_API void
redis_sock_set_auth(RedisSock * redis_sock,zend_string * user,zend_string * pass)188 redis_sock_set_auth(RedisSock *redis_sock, zend_string *user, zend_string *pass)
189 {
190     /* Release existing user/pass */
191     redis_sock_free_auth(redis_sock);
192 
193     /* Set new user/pass */
194     redis_sock->user = user ? zend_string_copy(user) : NULL;
195     redis_sock->pass = pass ? zend_string_copy(pass) : NULL;
196 }
197 
198 
199 PHP_REDIS_API void
redis_sock_set_auth_zval(RedisSock * redis_sock,zval * zv)200 redis_sock_set_auth_zval(RedisSock *redis_sock, zval *zv) {
201     zend_string *user, *pass;
202 
203     if (redis_extract_auth_info(zv, &user, &pass) == FAILURE)
204         return;
205 
206     redis_sock_set_auth(redis_sock, user, pass);
207 
208     if (user) zend_string_release(user);
209     if (pass) zend_string_release(pass);
210 }
211 
212 PHP_REDIS_API void
redis_sock_free_auth(RedisSock * redis_sock)213 redis_sock_free_auth(RedisSock *redis_sock) {
214     if (redis_sock->user) {
215         zend_string_release(redis_sock->user);
216         redis_sock->user = NULL;
217     }
218 
219     if (redis_sock->pass) {
220         zend_string_release(redis_sock->pass);
221         redis_sock->pass = NULL;
222     }
223 }
224 
225 PHP_REDIS_API char *
redis_sock_auth_cmd(RedisSock * redis_sock,int * cmdlen)226 redis_sock_auth_cmd(RedisSock *redis_sock, int *cmdlen) {
227     char *cmd;
228 
229     /* AUTH requires at least a password */
230     if (redis_sock->pass == NULL)
231         return NULL;
232 
233     if (redis_sock->user) {
234         *cmdlen = redis_spprintf(redis_sock, NULL, &cmd, "AUTH", "SS", redis_sock->user, redis_sock->pass);
235     } else {
236         *cmdlen = redis_spprintf(redis_sock, NULL, &cmd, "AUTH", "S", redis_sock->pass);
237     }
238 
239     return cmd;
240 }
241 
242 /* Send Redis AUTH and process response */
redis_sock_auth(RedisSock * redis_sock)243 PHP_REDIS_API int redis_sock_auth(RedisSock *redis_sock) {
244     char *cmd;
245     int cmdlen, rv = FAILURE;
246 
247     if ((cmd = redis_sock_auth_cmd(redis_sock, &cmdlen)) == NULL)
248         return SUCCESS;
249 
250     if (redis_sock_write(redis_sock, cmd, cmdlen) < 0)
251         goto cleanup;
252 
253     rv = redis_sock_read_ok(redis_sock) == SUCCESS ? SUCCESS : FAILURE;
254 cleanup:
255     efree(cmd);
256     return rv;
257 }
258 
259 /* Helper function and macro to test a RedisSock error prefix. */
260 #define REDIS_SOCK_ERRCMP_STATIC(rs, s) redis_sock_errcmp(rs, s, sizeof(s)-1)
redis_sock_errcmp(RedisSock * redis_sock,const char * err,size_t errlen)261 static int redis_sock_errcmp(RedisSock *redis_sock, const char *err, size_t errlen) {
262     return ZSTR_LEN(redis_sock->err) >= errlen &&
263            memcmp(ZSTR_VAL(redis_sock->err), err, errlen) == 0;
264 }
265 
266 /* Helper function that will throw an exception for a small number of ERR codes
267  * returned by Redis.  Typically we just return FALSE to the caller in the event
268  * of an ERROR reply, but for the following error types:
269  *    1) MASTERDOWN
270  *    2) AUTH
271  *    3) LOADING
272  */
273 static void
redis_error_throw(RedisSock * redis_sock)274 redis_error_throw(RedisSock *redis_sock)
275 {
276     /* Short circuit if we have no redis_sock or any error */
277     if (redis_sock == NULL || redis_sock->err == NULL)
278         return;
279 
280     /* Redis 6 decided to add 'ERR AUTH' which has a normal 'ERR' prefix
281      * but is actually an authentication error that we will want to throw
282      * an exception for, so just short circuit if this is any other 'ERR'
283      * prefixed error. */
284     if (REDIS_SOCK_ERRCMP_STATIC(redis_sock, "ERR") &&
285         !REDIS_SOCK_ERRCMP_STATIC(redis_sock, "ERR AUTH")) return;
286 
287     /* We may want to flip this logic and check for MASTERDOWN, AUTH,
288      * and LOADING but that may have side effects (esp for things like
289      * Disque) */
290     if (!REDIS_SOCK_ERRCMP_STATIC(redis_sock, "NOSCRIPT") &&
291         !REDIS_SOCK_ERRCMP_STATIC(redis_sock, "NOQUORUM") &&
292         !REDIS_SOCK_ERRCMP_STATIC(redis_sock, "NOGOODSLAVE") &&
293         !REDIS_SOCK_ERRCMP_STATIC(redis_sock, "WRONGTYPE") &&
294         !REDIS_SOCK_ERRCMP_STATIC(redis_sock, "BUSYGROUP") &&
295         !REDIS_SOCK_ERRCMP_STATIC(redis_sock, "NOGROUP"))
296     {
297         REDIS_THROW_EXCEPTION(ZSTR_VAL(redis_sock->err), 0);
298     }
299 }
300 
301 PHP_REDIS_API int
redis_check_eof(RedisSock * redis_sock,int no_throw)302 redis_check_eof(RedisSock *redis_sock, int no_throw)
303 {
304     unsigned int retry_index;
305     char *errmsg;
306 
307     if (!redis_sock || !redis_sock->stream || redis_sock->status == REDIS_SOCK_STATUS_FAILED) {
308         if (!no_throw) {
309             REDIS_THROW_EXCEPTION( "Connection closed", 0);
310         }
311         return -1;
312     }
313 
314     /* NOITCE: set errno = 0 here
315      *
316      * There is a bug in php socket stream to check liveness of a connection:
317      * if (0 >= recv(sock->socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EWOULDBLOCK) {
318      *    alive = 0;
319      * }
320      * If last errno is EWOULDBLOCK and recv returns 0 because of connection closed, alive would not be
321      * set to 0. However, the connection is close indeed. The php_stream_eof is not reliable. This will
322      * cause a "read error on connection" exception when use a closed persistent connection.
323      *
324      * We work around this by set errno = 0 first.
325      *
326      * Bug fix of php: https://github.com/php/php-src/pull/1456
327      * */
328     errno = 0;
329     if (php_stream_eof(redis_sock->stream) == 0) {
330         /* Success */
331         return 0;
332     } else if (redis_sock->mode == MULTI || redis_sock->watching) {
333         errmsg = "Connection lost and socket is in MULTI/watching mode";
334     } else {
335         errmsg = "Connection lost";
336         redis_backoff_reset(&redis_sock->backoff);
337         for (retry_index = 0; retry_index < redis_sock->max_retries; ++retry_index) {
338             /* close existing stream before reconnecting */
339             if (redis_sock->stream) {
340                 redis_sock_disconnect(redis_sock, 1);
341             }
342             /* Sleep based on our backoff algorithm */
343             zend_ulong delay = redis_backoff_compute(&redis_sock->backoff, retry_index);
344             if (delay != 0)
345                 usleep(delay);
346 
347             /* reconnect */
348             if (redis_sock_connect(redis_sock) == 0) {
349                 /* check for EOF again. */
350                 errno = 0;
351                 if (php_stream_eof(redis_sock->stream) == 0) {
352                     if (redis_sock_auth(redis_sock) != SUCCESS) {
353                         errmsg = "AUTH failed while reconnecting";
354                         break;
355                     }
356 
357                     redis_sock->status = REDIS_SOCK_STATUS_READY;
358                     /* If we're using a non-zero db, reselect it */
359                     if (redis_sock->dbNumber && reselect_db(redis_sock) != 0) {
360                         errmsg = "SELECT failed while reconnecting";
361                         break;
362                     }
363                     /* Success */
364                     return 0;
365                 }
366             }
367         }
368     }
369     /* close stream and mark socket as failed */
370     redis_sock_disconnect(redis_sock, 1);
371     redis_sock->status = REDIS_SOCK_STATUS_FAILED;
372     if (!no_throw) {
373         REDIS_THROW_EXCEPTION( errmsg, 0);
374     }
375     return -1;
376 }
377 
378 
379 PHP_REDIS_API int
redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,REDIS_SCAN_TYPE type,zend_long * iter)380 redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
381                            REDIS_SCAN_TYPE type, zend_long *iter)
382 {
383     REDIS_REPLY_TYPE reply_type;
384     long reply_info;
385     char *p_iter;
386 
387     /* Our response should have two multibulk replies */
388     if(redis_read_reply_type(redis_sock, &reply_type, &reply_info)<0
389        || reply_type != TYPE_MULTIBULK || reply_info != 2)
390     {
391         return -1;
392     }
393 
394     /* The BULK response iterator */
395     if(redis_read_reply_type(redis_sock, &reply_type, &reply_info)<0
396        || reply_type != TYPE_BULK)
397     {
398         return -1;
399     }
400 
401     /* Attempt to read the iterator */
402     if(!(p_iter = redis_sock_read_bulk_reply(redis_sock, reply_info))) {
403         return -1;
404     }
405 
406     /* Push the iterator out to the caller */
407     *iter = atol(p_iter);
408     efree(p_iter);
409 
410     /* Read our actual keys/members/etc differently depending on what kind of
411        scan command this is.  They all come back in slightly different ways */
412     switch(type) {
413         case TYPE_SCAN:
414             return redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU,
415                 redis_sock, NULL, NULL);
416         case TYPE_SSCAN:
417             return redis_sock_read_multibulk_reply(
418                 INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
419         case TYPE_ZSCAN:
420             return redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAM_PASSTHRU,
421                 redis_sock, NULL, NULL);
422         case TYPE_HSCAN:
423             return redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAM_PASSTHRU,
424                 redis_sock, NULL, NULL);
425         default:
426             return -1;
427     }
428 }
429 
redis_subscribe_response(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)430 PHP_REDIS_API int redis_subscribe_response(INTERNAL_FUNCTION_PARAMETERS,
431                                     RedisSock *redis_sock, zval *z_tab,
432                                     void *ctx)
433 {
434     subscribeContext *sctx = (subscribeContext*)ctx;
435     zval *z_tmp, z_resp;
436 
437     // Consume response(s) from subscribe, which will vary on argc
438     while(sctx->argc--) {
439         if (!redis_sock_read_multibulk_reply_zval(
440             INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp)
441         ) {
442             efree(sctx);
443             return -1;
444         }
445 
446         // We'll need to find the command response
447         if ((z_tmp = zend_hash_index_find(Z_ARRVAL(z_resp), 0)) == NULL) {
448             zval_dtor(&z_resp);
449             efree(sctx);
450             return -1;
451         }
452 
453         // Make sure the command response matches the command we called
454         if(strcasecmp(Z_STRVAL_P(z_tmp), sctx->kw) !=0) {
455             zval_dtor(&z_resp);
456             efree(sctx);
457             return -1;
458         }
459 
460         zval_dtor(&z_resp);
461     }
462 
463     zval z_ret, z_args[4];
464     sctx->cb.retval = &z_ret;
465     sctx->cb.params = z_args;
466 
467     /* Multibulk response, {[pattern], type, channel, payload } */
468     while(1) {
469         zval *z_type, *z_chan, *z_pat = NULL, *z_data;
470         HashTable *ht_tab;
471         int tab_idx=1, is_pmsg;
472 
473         if (!redis_sock_read_multibulk_reply_zval(
474             INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp)) break;
475 
476         ht_tab = Z_ARRVAL(z_resp);
477 
478         if ((z_type = zend_hash_index_find(ht_tab, 0)) == NULL ||
479            Z_TYPE_P(z_type) != IS_STRING
480         ) {
481             break;
482         }
483 
484         // Check for message or pmessage
485         if(!strncmp(Z_STRVAL_P(z_type), "message", 7) ||
486            !strncmp(Z_STRVAL_P(z_type), "pmessage", 8))
487         {
488             is_pmsg = *Z_STRVAL_P(z_type)=='p';
489         } else {
490             break;
491         }
492 
493         // Extract pattern if it's a pmessage
494         if(is_pmsg) {
495             if ((z_pat = zend_hash_index_find(ht_tab, tab_idx++)) == NULL) {
496                 break;
497             }
498         }
499 
500         // Extract channel and data
501         if ((z_chan = zend_hash_index_find(ht_tab, tab_idx++)) == NULL ||
502             (z_data = zend_hash_index_find(ht_tab, tab_idx++)) == NULL
503         ) {
504             break;
505         }
506 
507         // Different args for SUBSCRIBE and PSUBSCRIBE
508         z_args[0] = *getThis();
509         if(is_pmsg) {
510             z_args[1] = *z_pat;
511             z_args[2] = *z_chan;
512             z_args[3] = *z_data;
513         } else {
514             z_args[1] = *z_chan;
515             z_args[2] = *z_data;
516         }
517 
518         // Set arg count
519         sctx->cb.param_count = tab_idx;
520 
521         // Execute callback
522         if(zend_call_function(&(sctx->cb), &(sctx->cb_cache))
523                               ==FAILURE)
524         {
525             break;
526         }
527 
528         // If we have a return value free it
529         zval_ptr_dtor(&z_ret);
530         zval_dtor(&z_resp);
531     }
532 
533     // This is an error state, clean up
534     zval_dtor(&z_resp);
535     efree(sctx);
536 
537     return -1;
538 }
539 
redis_unsubscribe_response(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)540 PHP_REDIS_API int redis_unsubscribe_response(INTERNAL_FUNCTION_PARAMETERS,
541                                       RedisSock *redis_sock, zval *z_tab,
542                                       void *ctx)
543 {
544     subscribeContext *sctx = (subscribeContext*)ctx;
545     zval *z_chan, zv, *z_ret = &zv, z_resp;
546     int i;
547 
548     array_init(z_ret);
549 
550     for (i = 0; i < sctx->argc; i++) {
551         if (!redis_sock_read_multibulk_reply_zval(
552             INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp) ||
553             (z_chan = zend_hash_index_find(Z_ARRVAL(z_resp), 1)) == NULL
554         ) {
555             zval_dtor(z_ret);
556             return -1;
557         }
558 
559         add_assoc_bool(z_ret, Z_STRVAL_P(z_chan), 1);
560 
561         zval_dtor(&z_resp);
562     }
563 
564     efree(sctx);
565 
566     RETVAL_ZVAL(z_ret, 0, 1);
567 
568     // Success
569     return 0;
570 }
571 
572 PHP_REDIS_API zval *
redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab)573 redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS,
574                                      RedisSock *redis_sock, zval *z_tab)
575 {
576     char inbuf[4096];
577     int numElems;
578     size_t len;
579 
580     ZVAL_NULL(z_tab);
581     if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
582         return NULL;
583     }
584 
585     if(inbuf[0] != '*') {
586         return NULL;
587     }
588     numElems = atoi(inbuf+1);
589 
590     array_init(z_tab);
591 
592     redis_mbulk_reply_loop(redis_sock, z_tab, numElems, UNSERIALIZE_ALL);
593 
594     return z_tab;
595 }
596 
597 /**
598  * redis_sock_read_bulk_reply
599  */
600 PHP_REDIS_API char *
redis_sock_read_bulk_reply(RedisSock * redis_sock,int bytes)601 redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes)
602 {
603     int offset = 0, nbytes;
604     char *reply;
605     size_t got;
606 
607     if (-1 == bytes || -1 == redis_check_eof(redis_sock, 0)) {
608         return NULL;
609     }
610 
611     nbytes = bytes + 2;
612     /* Allocate memory for string */
613     reply = emalloc(nbytes);
614 
615     /* Consume bulk string */
616     while (offset < nbytes) {
617         got = php_stream_read(redis_sock->stream, reply + offset, nbytes - offset);
618         if (got == 0 && php_stream_eof(redis_sock->stream)) break;
619         offset += got;
620     }
621 
622     /* Protect against reading too few bytes */
623     if (offset < nbytes) {
624         /* Error or EOF */
625         REDIS_THROW_EXCEPTION("socket error on read socket", 0);
626         efree(reply);
627         return NULL;
628     }
629 
630     /* Null terminate reply string */
631     reply[bytes] = '\0';
632 
633     return reply;
634 }
635 
636 /**
637  * redis_sock_read
638  */
639 PHP_REDIS_API char *
redis_sock_read(RedisSock * redis_sock,int * buf_len)640 redis_sock_read(RedisSock *redis_sock, int *buf_len)
641 {
642     char inbuf[4096];
643     size_t len;
644 
645     *buf_len = 0;
646     if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
647         return NULL;
648     }
649 
650     switch(inbuf[0]) {
651         case '-':
652             redis_sock_set_err(redis_sock, inbuf+1, len);
653 
654             /* Filter our ERROR through the few that should actually throw */
655             redis_error_throw(redis_sock);
656 
657             return NULL;
658         case '$':
659             *buf_len = atoi(inbuf + 1);
660             return redis_sock_read_bulk_reply(redis_sock, *buf_len);
661 
662         case '*':
663             /* For null multi-bulk replies (like timeouts from brpoplpush): */
664             if(memcmp(inbuf + 1, "-1", 2) == 0) {
665                 return NULL;
666             }
667             /* fall through */
668 
669         case '+':
670         case ':':
671             /* Single Line Reply */
672             /* +OK or :123 */
673             if (len > 1) {
674                 *buf_len = len;
675                 return estrndup(inbuf, *buf_len);
676             }
677         default:
678             zend_throw_exception_ex(redis_exception_ce, 0,
679                 "protocol error, got '%c' as reply type byte\n",
680                 inbuf[0]
681             );
682     }
683 
684     return NULL;
685 }
686 
687 /* A simple union to store the various arg types we might handle in our
688  * redis_spprintf command formatting function */
689 union resparg {
690     char *str;
691     zend_string *zstr;
692     zval *zv;
693     int ival;
694     long lval;
695     double dval;
696 };
697 
redis_hash_auth(zend_string * user,zend_string * pass)698 static zend_string *redis_hash_auth(zend_string *user, zend_string *pass) {
699     zend_string *algo, *hex;
700     smart_str salted = {0};
701     const php_hash_ops *ops;
702     unsigned char *digest;
703     void *ctx;
704 
705     /* No op if there is not username/password */
706     if (user == NULL && pass == NULL)
707         return NULL;
708 
709     /* Theoretically inpossible but check anyway */
710     algo = zend_string_init("sha256", sizeof("sha256") - 1, 0);
711     if ((ops = redis_hash_fetch_ops(algo)) == NULL) {
712         zend_string_release(algo);
713         return NULL;
714     }
715 
716     /* Hash username + password with our salt global */
717     smart_str_alloc(&salted, 256, 0);
718     if (user) smart_str_append_ex(&salted, user, 0);
719     if (pass) smart_str_append_ex(&salted, pass, 0);
720     smart_str_appendl_ex(&salted, REDIS_G(salt), sizeof(REDIS_G(salt)), 0);
721 
722     ctx = emalloc(ops->context_size);
723 #if PHP_VERSION_ID >= 80100
724     ops->hash_init(ctx,NULL);
725 #else
726     ops->hash_init(ctx);
727 #endif
728     ops->hash_update(ctx, (const unsigned char *)ZSTR_VAL(salted.s), ZSTR_LEN(salted.s));
729 
730     digest = emalloc(ops->digest_size);
731     ops->hash_final(digest, ctx);
732     efree(ctx);
733 
734     hex = zend_string_safe_alloc(ops->digest_size, 2, 0, 0);
735     php_hash_bin2hex(ZSTR_VAL(hex), digest, ops->digest_size);
736     ZSTR_VAL(hex)[2 * ops->digest_size] = 0;
737 
738     efree(digest);
739     zend_string_release(algo);
740     smart_str_free(&salted);
741 
742     return hex;
743 }
744 
append_auth_hash(smart_str * dst,zend_string * user,zend_string * pass)745 static void append_auth_hash(smart_str *dst, zend_string *user, zend_string *pass) {
746     zend_string *s;
747 
748     if ((s = redis_hash_auth(user, pass)) != NULL) {
749         smart_str_appendc(dst, ':');
750         smart_str_append_ex(dst, s, 0);
751         zend_string_release(s);
752     }
753 }
754 
755 /* A printf like function to generate our connection pool hash value. */
756 PHP_REDIS_API zend_string *
redis_pool_spprintf(RedisSock * redis_sock,char * fmt,...)757 redis_pool_spprintf(RedisSock *redis_sock, char *fmt, ...) {
758     smart_str str = {0};
759 
760     smart_str_alloc(&str, 128, 0);
761 
762     /* We always include phpredis_<host>:<port> */
763     smart_str_appendl(&str, "phpredis_", sizeof("phpredis_") - 1);
764     smart_str_append_ex(&str, redis_sock->host, 0);
765     smart_str_appendc(&str, ':');
766     smart_str_append_long(&str, (zend_long)redis_sock->port);
767 
768     /* Short circuit if we don't have a pattern */
769     if (fmt == NULL) {
770         smart_str_0(&str);
771         return str.s;
772     }
773 
774     while (*fmt) {
775         switch (*fmt) {
776             case 'i':
777                 if (redis_sock->persistent_id) {
778                     smart_str_appendc(&str, ':');
779                     smart_str_append_ex(&str, redis_sock->persistent_id, 0);
780                 }
781                 break;
782             case 'u':
783                 smart_str_appendc(&str, ':');
784                 if (redis_sock->user) {
785                     smart_str_append_ex(&str, redis_sock->user, 0);
786                 }
787                 break;
788             case 'p':
789                 append_auth_hash(&str, NULL, redis_sock->pass);
790                 break;
791             case 'a':
792                 append_auth_hash(&str, redis_sock->user, redis_sock->pass);
793                 break;
794             default:
795                 /* Maybe issue a php_error_docref? */
796                 break;
797         }
798 
799         fmt++;
800     }
801 
802     smart_str_0(&str);
803     return str.s;
804 }
805 
806 /* A printf like method to construct a Redis RESP command.  It has been extended
807  * to take a few different format specifiers that are convenient to phpredis.
808  *
809  * s - C string followed by length as a
810  * S - Pointer to a zend_string
811  * k - Same as 's' but the value will be prefixed if phpredis is set up do do
812  *     that and the working slot will be set if it has been passed.
813  * v - A z_val which will be serialized if phpredis is configured to serialize.
814  * f - A double value
815  * F - Alias to 'f'
816  * i - An integer
817  * d - Alias to 'i'
818  * l - A long
819  * L - Alias to 'l'
820  */
821 PHP_REDIS_API int
redis_spprintf(RedisSock * redis_sock,short * slot,char ** ret,char * kw,char * fmt,...)822 redis_spprintf(RedisSock *redis_sock, short *slot, char **ret, char *kw, char *fmt, ...) {
823     smart_string cmd = {0};
824     va_list ap;
825     union resparg arg;
826     char *dup;
827     int argfree;
828     size_t arglen;
829 
830     va_start(ap, fmt);
831 
832     /* Header */
833     redis_cmd_init_sstr(&cmd, strlen(fmt), kw, strlen(kw));
834 
835     while (*fmt) {
836         switch (*fmt) {
837             case 's':
838                 arg.str = va_arg(ap, char*);
839                 arglen = va_arg(ap, size_t);
840                 redis_cmd_append_sstr(&cmd, arg.str, arglen);
841                 break;
842             case 'S':
843                 arg.zstr = va_arg(ap, zend_string*);
844                 redis_cmd_append_sstr(&cmd, ZSTR_VAL(arg.zstr), ZSTR_LEN(arg.zstr));
845                 break;
846             case 'k':
847                 arg.str = va_arg(ap, char*);
848                 arglen = va_arg(ap, size_t);
849                 argfree = redis_key_prefix(redis_sock, &arg.str, &arglen);
850                 redis_cmd_append_sstr(&cmd, arg.str, arglen);
851                 if (slot) *slot = cluster_hash_key(arg.str, arglen);
852                 if (argfree) efree(arg.str);
853                 break;
854             case 'v':
855                 arg.zv = va_arg(ap, zval*);
856                 argfree = redis_pack(redis_sock, arg.zv, &dup, &arglen);
857                 redis_cmd_append_sstr(&cmd, dup, arglen);
858                 if (argfree) efree(dup);
859                 break;
860             case 'f':
861             case 'F':
862                 arg.dval = va_arg(ap, double);
863                 redis_cmd_append_sstr_dbl(&cmd, arg.dval);
864                 break;
865             case 'i':
866             case 'd':
867                 arg.ival = va_arg(ap, int);
868                 redis_cmd_append_sstr_int(&cmd, arg.ival);
869                 break;
870             case 'l':
871             case 'L':
872                 arg.lval = va_arg(ap, long);
873                 redis_cmd_append_sstr_long(&cmd, arg.lval);
874                 break;
875         }
876 
877         fmt++;
878     }
879     /* varargs cleanup */
880     va_end(ap);
881 
882     /* Null terminate */
883     smart_string_0(&cmd);
884 
885     /* Push command string, return length */
886     *ret = cmd.c;
887     return cmd.len;
888 }
889 
890 /*
891  * Given a smart string, number of arguments, a keyword, and the length of the keyword
892  * initialize our smart string with the proper Redis header for the command to follow
893  */
redis_cmd_init_sstr(smart_string * str,int num_args,char * keyword,int keyword_len)894 int redis_cmd_init_sstr(smart_string *str, int num_args, char *keyword, int keyword_len) {
895     smart_string_appendc(str, '*');
896     smart_string_append_long(str, num_args + 1);
897     smart_string_appendl(str, _NL, sizeof(_NL) -1);
898     smart_string_appendc(str, '$');
899     smart_string_append_long(str, keyword_len);
900     smart_string_appendl(str, _NL, sizeof(_NL) - 1);
901     smart_string_appendl(str, keyword, keyword_len);
902     smart_string_appendl(str, _NL, sizeof(_NL) - 1);
903     return str->len;
904 }
905 
906 /*
907  * Append a command sequence to a smart_string
908  */
redis_cmd_append_sstr(smart_string * str,char * append,int append_len)909 int redis_cmd_append_sstr(smart_string *str, char *append, int append_len) {
910     smart_string_appendc(str, '$');
911     smart_string_append_long(str, append_len);
912     smart_string_appendl(str, _NL, sizeof(_NL) - 1);
913     smart_string_appendl(str, append, append_len);
914     smart_string_appendl(str, _NL, sizeof(_NL) - 1);
915 
916     /* Return our new length */
917     return str->len;
918 }
919 
920 /*
921  * Append an integer to a smart string command
922  */
redis_cmd_append_sstr_int(smart_string * str,int append)923 int redis_cmd_append_sstr_int(smart_string *str, int append) {
924     char int_buf[32];
925     int int_len = snprintf(int_buf, sizeof(int_buf), "%d", append);
926     return redis_cmd_append_sstr(str, int_buf, int_len);
927 }
928 
929 /*
930  * Append a long to a smart string command
931  */
redis_cmd_append_sstr_long(smart_string * str,long append)932 int redis_cmd_append_sstr_long(smart_string *str, long append) {
933     char long_buf[32];
934     int long_len = snprintf(long_buf, sizeof(long_buf), "%ld", append);
935     return redis_cmd_append_sstr(str, long_buf, long_len);
936 }
937 
938 /*
939  * Append a 64-bit integer to our command
940  */
redis_cmd_append_sstr_i64(smart_string * str,int64_t append)941 int redis_cmd_append_sstr_i64(smart_string *str, int64_t append) {
942     char nbuf[64];
943     int len = snprintf(nbuf, sizeof(nbuf), "%" PRId64, append);
944     return redis_cmd_append_sstr(str, nbuf, len);
945 }
946 
947 /*
948  * Append a double to a smart string command
949  */
950 int
redis_cmd_append_sstr_dbl(smart_string * str,double value)951 redis_cmd_append_sstr_dbl(smart_string *str, double value)
952 {
953     char tmp[64], *p;
954     int len;
955 
956     /* Convert to string */
957     len = snprintf(tmp, sizeof(tmp), "%.17g", value);
958 
959     /* snprintf depends on locale, replace comma with point */
960     if ((p = strchr(tmp, ',')) != NULL) *p = '.';
961 
962     // Append the string
963     return redis_cmd_append_sstr(str, tmp, len);
964 }
965 
966 /* Append a zval to a redis command.  The value will be serialized if we are
967  * configured to do that */
redis_cmd_append_sstr_zval(smart_string * str,zval * z,RedisSock * redis_sock)968 int redis_cmd_append_sstr_zval(smart_string *str, zval *z, RedisSock *redis_sock) {
969     char *val;
970     size_t vallen;
971     int valfree, retval;
972 
973     valfree = redis_pack(redis_sock, z, &val, &vallen);
974     retval = redis_cmd_append_sstr(str, val, vallen);
975     if (valfree) efree(val);
976 
977     return retval;
978 }
979 
redis_cmd_append_sstr_zstr(smart_string * str,zend_string * zstr)980 int redis_cmd_append_sstr_zstr(smart_string *str, zend_string *zstr) {
981     return redis_cmd_append_sstr(str, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
982 }
983 
984 /* Append a string key to a redis command.  This function takes care of prefixing the key
985  * for the caller and setting the slot argument if it is passed non null */
redis_cmd_append_sstr_key(smart_string * str,char * key,size_t len,RedisSock * redis_sock,short * slot)986 int redis_cmd_append_sstr_key(smart_string *str, char *key, size_t len, RedisSock *redis_sock, short *slot) {
987     int valfree, retval;
988 
989     valfree = redis_key_prefix(redis_sock, &key, &len);
990     if (slot) *slot = cluster_hash_key(key, len);
991     retval = redis_cmd_append_sstr(str, key, len);
992     if (valfree) efree(key);
993 
994     return retval;
995 }
996 
997 /* Append an array key to a redis smart string command.  This function
998  * handles the boilerplate conditionals around string or integer keys */
redis_cmd_append_sstr_arrkey(smart_string * cmd,zend_string * kstr,zend_ulong idx)999 int redis_cmd_append_sstr_arrkey(smart_string *cmd, zend_string *kstr, zend_ulong idx)
1000 {
1001     char *arg, kbuf[128];
1002     int len;
1003 
1004     if (kstr) {
1005         len = ZSTR_LEN(kstr);
1006         arg = ZSTR_VAL(kstr);
1007     } else {
1008         len = snprintf(kbuf, sizeof(kbuf), "%ld", (long)idx);
1009         arg = (char*)kbuf;
1010     }
1011 
1012     return redis_cmd_append_sstr(cmd, arg, len);
1013 }
1014 
1015 PHP_REDIS_API int
redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1016 redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
1017 
1018     char *response;
1019     int response_len;
1020     double ret;
1021 
1022     if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) {
1023         if (IS_ATOMIC(redis_sock)) {
1024             RETVAL_FALSE;
1025         } else {
1026             add_next_index_bool(z_tab, 0);
1027         }
1028         return FAILURE;
1029     }
1030 
1031     ret = atof(response);
1032     efree(response);
1033     if (IS_ATOMIC(redis_sock)) {
1034         RETVAL_DOUBLE(ret);
1035     } else {
1036         add_next_index_double(z_tab, ret);
1037     }
1038 
1039     return SUCCESS;
1040 }
1041 
redis_type_response(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1042 PHP_REDIS_API int redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
1043     char *response;
1044     int response_len;
1045     long l;
1046 
1047     if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) {
1048         if (IS_ATOMIC(redis_sock)) {
1049             RETVAL_FALSE;
1050         } else {
1051             add_next_index_bool(z_tab, 0);
1052         }
1053         return FAILURE;
1054     }
1055 
1056     if (strncmp(response, "+string", 7) == 0) {
1057         l = REDIS_STRING;
1058     } else if (strncmp(response, "+set", 4) == 0){
1059         l = REDIS_SET;
1060     } else if (strncmp(response, "+list", 5) == 0){
1061         l = REDIS_LIST;
1062     } else if (strncmp(response, "+zset", 5) == 0){
1063         l = REDIS_ZSET;
1064     } else if (strncmp(response, "+hash", 5) == 0){
1065         l = REDIS_HASH;
1066     } else if (strncmp(response, "+stream", 7) == 0) {
1067         l = REDIS_STREAM;
1068     } else {
1069         l = REDIS_NOT_FOUND;
1070     }
1071 
1072     efree(response);
1073     if (IS_ATOMIC(redis_sock)) {
1074         RETVAL_LONG(l);
1075     } else {
1076         add_next_index_long(z_tab, l);
1077     }
1078 
1079     return SUCCESS;
1080 }
1081 
redis_info_response(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1082 PHP_REDIS_API int redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
1083     char *response;
1084     int response_len;
1085     zval z_ret;
1086 
1087     /* Read bulk response */
1088     if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) {
1089         RETVAL_FALSE;
1090         return FAILURE;
1091     }
1092 
1093     /* Parse it into a zval array */
1094     ZVAL_UNDEF(&z_ret);
1095     redis_parse_info_response(response, &z_ret);
1096 
1097     /* Free source response */
1098     efree(response);
1099 
1100     if (IS_ATOMIC(redis_sock)) {
1101         RETVAL_ZVAL(&z_ret, 0, 1);
1102     } else {
1103         add_next_index_zval(z_tab, &z_ret);
1104     }
1105 
1106     return SUCCESS;
1107 }
1108 
1109 PHP_REDIS_API void
redis_parse_info_response(char * response,zval * z_ret)1110 redis_parse_info_response(char *response, zval *z_ret)
1111 {
1112     char *cur, *pos;
1113 
1114     array_init(z_ret);
1115 
1116     cur = response;
1117     while(1) {
1118         /* skip comments and empty lines */
1119         if (*cur == '#' || *cur == '\r') {
1120             if ((cur = strstr(cur, _NL)) == NULL) {
1121                 break;
1122             }
1123             cur += 2;
1124             continue;
1125         }
1126 
1127         /* key */
1128         if ((pos = strchr(cur, ':')) == NULL) {
1129             break;
1130         }
1131         char *key = cur;
1132         int key_len = pos - cur;
1133         key[key_len] = '\0';
1134 
1135         /* value */
1136         cur = pos + 1;
1137         if ((pos = strstr(cur, _NL)) == NULL) {
1138             break;
1139         }
1140         char *value = cur;
1141         int value_len = pos - cur;
1142         value[value_len] = '\0';
1143 
1144         double dval;
1145         zend_long lval;
1146         zend_uchar type = is_numeric_string(value, value_len, &lval, &dval, 0);
1147         if (type == IS_LONG) {
1148             add_assoc_long_ex(z_ret, key, key_len, lval);
1149         } else if (type == IS_DOUBLE) {
1150             add_assoc_double_ex(z_ret, key, key_len, dval);
1151         } else {
1152             add_assoc_stringl_ex(z_ret, key, key_len, value, value_len);
1153         }
1154 
1155         cur = pos + 2; /* \r, \n */
1156     }
1157 }
1158 
1159 /*
1160  * Specialized handling of the CLIENT LIST output so it comes out in a simple way for PHP userland code
1161  * to handle.
1162  */
1163 PHP_REDIS_API int
redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1164 redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
1165     char *resp;
1166     int resp_len;
1167     zval z_ret;
1168 
1169     /* Make sure we can read the bulk response from Redis */
1170     if ((resp = redis_sock_read(redis_sock, &resp_len)) == NULL) {
1171         RETVAL_FALSE;
1172         return FAILURE;
1173     }
1174 
1175     /* Parse it out */
1176     redis_parse_client_list_response(resp, &z_ret);
1177 
1178     /* Free our response */
1179     efree(resp);
1180 
1181     /* Return or append depending if we're atomic */
1182     if (IS_ATOMIC(redis_sock)) {
1183         RETVAL_ZVAL(&z_ret, 0, 1);
1184     } else {
1185         add_next_index_zval(z_tab, &z_ret);
1186     }
1187 
1188     return SUCCESS;
1189 }
1190 
1191 PHP_REDIS_API void
redis_parse_client_list_response(char * response,zval * z_ret)1192 redis_parse_client_list_response(char *response, zval *z_ret)
1193 {
1194     char *p, *lpos, *kpos = NULL, *vpos = NULL, *p2, *key, *value;
1195     int klen = 0, done = 0, is_numeric;
1196     zval z_sub_result;
1197 
1198     /* Allocate for response and our user */
1199     array_init(z_ret);
1200     array_init(&z_sub_result);
1201 
1202     // Pointers for parsing
1203     p = response;
1204     lpos = response;
1205 
1206     /* While we've got more to parse */
1207     while(!done) {
1208         /* What character are we on */
1209         switch(*p) {
1210             /* We're done */
1211             case '\0':
1212                 done = 1;
1213                 break;
1214             /* \n, ' ' mean we can pull a k/v pair */
1215             case '\n':
1216             case ' ':
1217                 /* Grab our value */
1218                 vpos = lpos;
1219 
1220                 /* There is some communication error or Redis bug if we don't
1221                    have a key and value, but check anyway. */
1222                 if(kpos && vpos) {
1223                     /* Allocate, copy in our key */
1224                     key = estrndup(kpos, klen);
1225 
1226                     /* Allocate, copy in our value */
1227                     value = estrndup(lpos, p - lpos);
1228 
1229                     /* Treat numbers as numbers, strings as strings */
1230                     is_numeric = 1;
1231                     for(p2 = value; *p2; ++p2) {
1232                         if(*p2 < '0' || *p2 > '9') {
1233                             is_numeric = 0;
1234                             break;
1235                         }
1236                     }
1237 
1238                     /* Add as a long or string, depending */
1239                     if(is_numeric == 1) {
1240                         add_assoc_long(&z_sub_result, key, atol(value));
1241                     } else {
1242                         add_assoc_string(&z_sub_result, key, value);
1243                     }
1244                     efree(value);
1245                     // If we hit a '\n', then we can add this user to our list
1246                     if(*p == '\n') {
1247                         /* Add our user */
1248                         add_next_index_zval(z_ret, &z_sub_result);
1249 
1250                         /* If we have another user, make another one */
1251                         if(*(p+1) != '\0') {
1252                             array_init(&z_sub_result);
1253                         }
1254                     }
1255 
1256                     // Free our key
1257                     efree(key);
1258                 } else {
1259                     // Something is wrong
1260                     zval_dtor(z_ret);
1261                     ZVAL_BOOL(z_ret, 0);
1262                     return;
1263                 }
1264 
1265                 /* Move forward */
1266                 lpos = p + 1;
1267 
1268                 break;
1269             /* We can pull the key and null terminate at our sep */
1270             case '=':
1271                 /* Key, key length */
1272                 kpos = lpos;
1273                 klen = p - lpos;
1274 
1275                 /* Move forward */
1276                 lpos = p + 1;
1277 
1278                 break;
1279         }
1280 
1281         /* Increment */
1282         p++;
1283     }
1284 }
1285 
1286 PHP_REDIS_API int
redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx,SuccessCallback success_callback)1287 redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1288                             zval *z_tab, void *ctx,
1289                             SuccessCallback success_callback)
1290 {
1291 
1292     char *response;
1293     int response_len;
1294     zend_bool ret = 0;
1295 
1296     if ((response = redis_sock_read(redis_sock, &response_len)) != NULL) {
1297         ret = (*response == '+');
1298         efree(response);
1299     }
1300 
1301     if (ret && success_callback != NULL) {
1302         success_callback(redis_sock);
1303     }
1304     if (IS_ATOMIC(redis_sock)) {
1305         RETVAL_BOOL(ret);
1306     } else {
1307         add_next_index_bool(z_tab, ret);
1308     }
1309 
1310     return ret ? SUCCESS : FAILURE;
1311 }
1312 
redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1313 PHP_REDIS_API int redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS,
1314                                    RedisSock *redis_sock, zval *z_tab,
1315                                    void *ctx)
1316 {
1317     return redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
1318                                        z_tab, ctx, NULL);
1319 }
1320 
redis_long_response(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1321 PHP_REDIS_API int redis_long_response(INTERNAL_FUNCTION_PARAMETERS,
1322                                       RedisSock *redis_sock, zval * z_tab,
1323                                       void *ctx)
1324 {
1325 
1326     char *response;
1327     int response_len;
1328 
1329     if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) {
1330         if (IS_ATOMIC(redis_sock)) {
1331             RETVAL_FALSE;
1332         } else {
1333             add_next_index_bool(z_tab, 0);
1334         }
1335 
1336         return FAILURE;
1337     }
1338 
1339     if(response[0] == ':') {
1340         int64_t ret = phpredis_atoi64(response + 1);
1341 
1342         if (IS_ATOMIC(redis_sock)) {
1343             if(ret > LONG_MAX) { /* overflow */
1344                 RETVAL_STRINGL(response + 1, response_len - 1);
1345             } else {
1346                 RETVAL_LONG((long)ret);
1347             }
1348         } else {
1349             if(ret > LONG_MAX) { /* overflow */
1350                 add_next_index_stringl(z_tab, response + 1, response_len - 1);
1351             } else {
1352                 add_next_index_long(z_tab, (long)ret);
1353             }
1354         }
1355     } else {
1356         if (IS_ATOMIC(redis_sock)) {
1357             RETVAL_FALSE;
1358         } else {
1359             add_next_index_null(z_tab);
1360         }
1361         efree(response);
1362         return FAILURE;
1363     }
1364 
1365     efree(response);
1366     return SUCCESS;
1367 }
1368 
1369 /* Helper method to convert [key, value, key, value] into [key => value,
1370  * key => value] when returning data to the caller.  Depending on our decode
1371  * flag we'll convert the value data types */
array_zip_values_and_scores(RedisSock * redis_sock,zval * z_tab,int decode)1372 static void array_zip_values_and_scores(RedisSock *redis_sock, zval *z_tab,
1373                                         int decode)
1374 {
1375 
1376     zval z_ret, z_sub;
1377     HashTable *keytable;
1378 
1379     array_init(&z_ret);
1380     keytable = Z_ARRVAL_P(z_tab);
1381 
1382     for(zend_hash_internal_pointer_reset(keytable);
1383         zend_hash_has_more_elements(keytable) == SUCCESS;
1384         zend_hash_move_forward(keytable)) {
1385 
1386         zval *z_key_p, *z_value_p;
1387 
1388         if ((z_key_p = zend_hash_get_current_data(keytable)) == NULL) {
1389             continue;   /* this should never happen, according to the PHP people. */
1390         }
1391 
1392         /* get current value, a key */
1393         zend_string *hkey = zval_get_string(z_key_p);
1394 
1395         /* move forward */
1396         zend_hash_move_forward(keytable);
1397 
1398         /* fetch again */
1399         if ((z_value_p = zend_hash_get_current_data(keytable)) == NULL) {
1400             zend_string_release(hkey);
1401             continue;   /* this should never happen, according to the PHP people. */
1402         }
1403 
1404         /* get current value, a hash value now. */
1405         char *hval = Z_STRVAL_P(z_value_p);
1406 
1407         /* Decode the score depending on flag */
1408         if (decode == SCORE_DECODE_INT && Z_STRLEN_P(z_value_p) > 0) {
1409             add_assoc_long_ex(&z_ret, ZSTR_VAL(hkey), ZSTR_LEN(hkey), atoi(hval+1));
1410         } else if (decode == SCORE_DECODE_DOUBLE) {
1411             add_assoc_double_ex(&z_ret, ZSTR_VAL(hkey), ZSTR_LEN(hkey), atof(hval));
1412         } else {
1413             ZVAL_ZVAL(&z_sub, z_value_p, 1, 0);
1414             add_assoc_zval_ex(&z_ret, ZSTR_VAL(hkey), ZSTR_LEN(hkey), &z_sub);
1415         }
1416         zend_string_release(hkey);
1417     }
1418 
1419     /* replace */
1420     zval_dtor(z_tab);
1421     ZVAL_ZVAL(z_tab, &z_ret, 0, 0);
1422 }
1423 
1424 static int
read_mbulk_header(RedisSock * redis_sock,int * nelem)1425 read_mbulk_header(RedisSock *redis_sock, int *nelem)
1426 {
1427     char line[4096];
1428     size_t len;
1429 
1430     /* Throws exception on failure */
1431     if (redis_sock_gets(redis_sock, line, sizeof(line)-1, &len) < 0)
1432         return -1;
1433 
1434     if (line[0] != '*') {
1435         if (IS_ATOMIC(redis_sock)) {
1436             if (line[0] == '-') {
1437                 redis_sock_set_err(redis_sock, line+1, len-1);
1438             }
1439         }
1440         return -1;
1441     }
1442 
1443     *nelem = atoi(line+1);
1444 
1445     return 0;
1446 }
1447 
1448 static int
redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,int unserialize,int decode)1449 redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1450                          zval *z_tab, int unserialize, int decode)
1451 {
1452     char inbuf[4096];
1453     int numElems;
1454     size_t len;
1455 
1456     if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
1457         return -1;
1458     }
1459 
1460     if(inbuf[0] != '*') {
1461         if (IS_ATOMIC(redis_sock)) {
1462             RETVAL_FALSE;
1463         } else {
1464             add_next_index_bool(z_tab, 0);
1465         }
1466         if (*inbuf == TYPE_ERR) {
1467             redis_sock_set_err(redis_sock, inbuf + 1, len - 1);
1468         }
1469         return -1;
1470     }
1471     numElems = atoi(inbuf+1);
1472     zval z_multi_result;
1473     array_init(&z_multi_result); /* pre-allocate array for multi's results. */
1474 
1475     /* Grab our key, value, key, value array */
1476     redis_mbulk_reply_loop(redis_sock, &z_multi_result, numElems, unserialize);
1477 
1478     /* Zip keys and values */
1479     array_zip_values_and_scores(redis_sock, &z_multi_result, decode);
1480 
1481     if (IS_ATOMIC(redis_sock)) {
1482         RETVAL_ZVAL(&z_multi_result, 0, 1);
1483     } else {
1484         add_next_index_zval(z_tab, &z_multi_result);
1485     }
1486 
1487     return 0;
1488 }
1489 
1490 /* Consume message ID */
1491 PHP_REDIS_API int
redis_sock_read_single_line(RedisSock * redis_sock,char * buffer,size_t buflen,size_t * linelen,int set_err)1492 redis_sock_read_single_line(RedisSock *redis_sock, char *buffer, size_t buflen,
1493                             size_t *linelen, int set_err)
1494 {
1495     REDIS_REPLY_TYPE type;
1496     long info;
1497 
1498     if (redis_read_reply_type(redis_sock, &type, &info) < 0 ||
1499         (type != TYPE_LINE && type != TYPE_ERR))
1500     {
1501         return -1;
1502     }
1503 
1504     if (redis_sock_gets(redis_sock, buffer, buflen, linelen) < 0) {
1505         return -1;
1506     }
1507 
1508     if (set_err && type == TYPE_ERR) {
1509         if (IS_ATOMIC(redis_sock)) {
1510             redis_sock_set_err(redis_sock, buffer, *linelen);
1511         }
1512     }
1513 
1514     return type == TYPE_LINE ? 0 : -1;
1515 }
1516 
1517 /* Helper function to consume Redis stream message data.  This is useful for
1518  * multiple stream callers (e.g. XREAD[GROUP], and X[REV]RANGE handlers). */
1519 PHP_REDIS_API int
redis_read_stream_messages(RedisSock * redis_sock,int count,zval * z_ret)1520 redis_read_stream_messages(RedisSock *redis_sock, int count, zval *z_ret
1521                           )
1522 {
1523     zval z_message;
1524     int i, mhdr, fields;
1525     char *id = NULL;
1526     int idlen;
1527 
1528     /* Iterate over each message */
1529     for (i = 0; i < count; i++) {
1530         /* Consume inner multi-bulk header, message ID itself and finally
1531          * the multi-bulk header for field and values */
1532         if ((read_mbulk_header(redis_sock, &mhdr) < 0 || mhdr != 2) ||
1533             ((id = redis_sock_read(redis_sock, &idlen)) == NULL) ||
1534             (read_mbulk_header(redis_sock, &fields) < 0 ||
1535             (fields > 0 && fields % 2 != 0)))
1536         {
1537             if (id) efree(id);
1538             return -1;
1539         }
1540 
1541         if (fields < 0) {
1542             add_assoc_null_ex(z_ret, id, idlen);
1543         } else {
1544             array_init(&z_message);
1545             redis_mbulk_reply_loop(redis_sock, &z_message, fields, UNSERIALIZE_VALS);
1546             array_zip_values_and_scores(redis_sock, &z_message, SCORE_DECODE_NONE);
1547             add_assoc_zval_ex(z_ret, id, idlen, &z_message);
1548         }
1549         efree(id);
1550     }
1551 
1552     return 0;
1553 }
1554 
1555 PHP_REDIS_API int
redis_xrange_reply(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1556 redis_xrange_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1557                    zval *z_tab, void *ctx)
1558 {
1559     zval z_messages;
1560     int messages;
1561 
1562     array_init(&z_messages);
1563 
1564     if (read_mbulk_header(redis_sock, &messages) < 0 ||
1565         redis_read_stream_messages(redis_sock, messages, &z_messages) < 0)
1566     {
1567         zval_dtor(&z_messages);
1568         if (IS_ATOMIC(redis_sock)) {
1569             RETVAL_FALSE;
1570         } else {
1571             add_next_index_bool(z_tab, 0);
1572         }
1573         return -1;
1574     }
1575 
1576     if (IS_ATOMIC(redis_sock)) {
1577         RETVAL_ZVAL(&z_messages, 0, 1);
1578     } else {
1579         add_next_index_zval(z_tab, &z_messages);
1580     }
1581 
1582     return 0;
1583 }
1584 
1585 PHP_REDIS_API int
redis_read_stream_messages_multi(RedisSock * redis_sock,int count,zval * z_streams)1586 redis_read_stream_messages_multi(RedisSock *redis_sock, int count, zval *z_streams
1587                                 )
1588 {
1589     zval z_messages;
1590     int i, shdr, messages;
1591     char *id = NULL;
1592     int idlen;
1593 
1594     for (i = 0; i < count; i++) {
1595         if ((read_mbulk_header(redis_sock, &shdr) < 0 || shdr != 2) ||
1596             (id = redis_sock_read(redis_sock, &idlen)) == NULL ||
1597             read_mbulk_header(redis_sock, &messages) < 0)
1598         {
1599             if (id) efree(id);
1600             return -1;
1601         }
1602 
1603         array_init(&z_messages);
1604 
1605         if (redis_read_stream_messages(redis_sock, messages, &z_messages) < 0)
1606             goto failure;
1607 
1608         add_assoc_zval_ex(z_streams, id, idlen, &z_messages);
1609         efree(id);
1610     }
1611 
1612     return 0;
1613 failure:
1614     efree(id);
1615     zval_dtor(&z_messages);
1616     return -1;
1617 }
1618 
1619 PHP_REDIS_API int
redis_xread_reply(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1620 redis_xread_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1621                   zval *z_tab, void *ctx)
1622 {
1623     zval z_rv;
1624     int streams;
1625 
1626     if (read_mbulk_header(redis_sock, &streams) < 0)
1627         goto failure;
1628 
1629     if (streams == -1 && redis_sock->null_mbulk_as_null) {
1630         ZVAL_NULL(&z_rv);
1631     } else {
1632         array_init(&z_rv);
1633         if (redis_read_stream_messages_multi(redis_sock, streams, &z_rv) < 0)
1634             goto cleanup;
1635     }
1636 
1637     if (IS_ATOMIC(redis_sock)) {
1638         RETVAL_ZVAL(&z_rv, 0, 1);
1639     } else {
1640         add_next_index_zval(z_tab, &z_rv);
1641     }
1642     return 0;
1643 
1644 cleanup:
1645     zval_dtor(&z_rv);
1646 failure:
1647     if (IS_ATOMIC(redis_sock)) {
1648         RETVAL_FALSE;
1649     } else {
1650         add_next_index_bool(z_tab, 0);
1651     }
1652     return -1;
1653 }
1654 
1655 /* This helper function does that actual XCLAIM response handling, which can be used by both
1656  * Redis and RedisCluster.  Note that XCLAIM is somewhat unique in that its reply type depends
1657  * on whether or not it was called with the JUSTID option */
1658 PHP_REDIS_API int
redis_read_xclaim_response(RedisSock * redis_sock,int count,zval * rv)1659 redis_read_xclaim_response(RedisSock *redis_sock, int count, zval *rv) {
1660     zval z_msg;
1661     REDIS_REPLY_TYPE type;
1662     char *id = NULL;
1663     int i, fields, idlen;
1664     long li;
1665 
1666     for (i = 0; i < count; i++) {
1667         /* Consume inner reply type */
1668         if (redis_read_reply_type(redis_sock, &type, &li) < 0 ||
1669             (type != TYPE_BULK && type != TYPE_MULTIBULK) ||
1670             (type == TYPE_BULK && li <= 0)) return -1;
1671 
1672         /* TYPE_BULK is the JUSTID variant, otherwise it's standard xclaim response */
1673         if (type == TYPE_BULK) {
1674             if ((id = redis_sock_read_bulk_reply(redis_sock, (size_t)li)) == NULL)
1675                 return -1;
1676 
1677             add_next_index_stringl(rv, id, li);
1678             efree(id);
1679         } else {
1680             if ((li != 2 || (id = redis_sock_read(redis_sock, &idlen)) == NULL) ||
1681                 (read_mbulk_header(redis_sock, &fields) < 0 || fields % 2 != 0))
1682             {
1683                 if (id) efree(id);
1684                 return -1;
1685             }
1686 
1687             array_init(&z_msg);
1688 
1689             redis_mbulk_reply_loop(redis_sock, &z_msg, fields, UNSERIALIZE_VALS);
1690             array_zip_values_and_scores(redis_sock, &z_msg, SCORE_DECODE_NONE);
1691             add_assoc_zval_ex(rv, id, idlen, &z_msg);
1692             efree(id);
1693         }
1694     }
1695 
1696     return 0;
1697 }
1698 
1699 PHP_REDIS_API int
redis_xclaim_reply(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1700 redis_xclaim_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1701                    zval *z_tab, void *ctx)
1702 {
1703     zval z_ret;
1704     int messages;
1705 
1706     /* All XCLAIM responses start multibulk */
1707     if (read_mbulk_header(redis_sock, &messages) < 0)
1708         goto failure;
1709 
1710     array_init(&z_ret);
1711 
1712     if (redis_read_xclaim_response(redis_sock, messages, &z_ret) < 0) {
1713         zval_dtor(&z_ret);
1714         goto failure;
1715     }
1716 
1717     if (IS_ATOMIC(redis_sock)) {
1718         RETVAL_ZVAL(&z_ret, 0, 1);
1719     } else {
1720         add_next_index_zval(z_tab, &z_ret);
1721     }
1722     return 0;
1723 
1724 failure:
1725     if (IS_ATOMIC(redis_sock)) {
1726         RETVAL_FALSE;
1727     } else {
1728         add_next_index_bool(z_tab, 0);
1729     }
1730     return -1;
1731 }
1732 
1733 PHP_REDIS_API int
redis_read_xinfo_response(RedisSock * redis_sock,zval * z_ret,int elements)1734 redis_read_xinfo_response(RedisSock *redis_sock, zval *z_ret, int elements)
1735 {
1736     zval zv;
1737     int i, len = 0;
1738     char *key = NULL, *data;
1739     REDIS_REPLY_TYPE type;
1740     long li;
1741 
1742     for (i = 0; i < elements; ++i) {
1743         if (redis_read_reply_type(redis_sock, &type, &li) < 0) {
1744             goto failure;
1745         }
1746         switch (type) {
1747         case TYPE_BULK:
1748             if ((data = redis_sock_read_bulk_reply(redis_sock, li)) == NULL) {
1749                 if (!key) goto failure;
1750                 add_assoc_null_ex(z_ret, key, len);
1751                 efree(key);
1752                 key = NULL;
1753             } else if (key) {
1754                 add_assoc_stringl_ex(z_ret, key, len, data, li);
1755                 efree(data);
1756                 efree(key);
1757                 key = NULL;
1758             } else {
1759                 key = data;
1760                 len = li;
1761             }
1762             break;
1763         case TYPE_INT:
1764             if (key) {
1765                 add_assoc_long_ex(z_ret, key, len, li);
1766                 efree(key);
1767                 key = NULL;
1768             } else {
1769                 len = spprintf(&key, 0, "%ld", li);
1770             }
1771             break;
1772         case TYPE_MULTIBULK:
1773             array_init(&zv);
1774             if (redis_read_xinfo_response(redis_sock, &zv, li) != SUCCESS) {
1775                 zval_dtor(&zv);
1776                 goto failure;
1777             }
1778             if (key) {
1779                 add_assoc_zval_ex(z_ret, key, len, &zv);
1780                 efree(key);
1781                 key = NULL;
1782             } else {
1783                 add_next_index_zval(z_ret, &zv);
1784             }
1785             break;
1786         default:
1787             goto failure;
1788         }
1789     }
1790 
1791     return SUCCESS;
1792 
1793 failure:
1794     if (key) efree(key);
1795     return FAILURE;
1796 }
1797 
1798 PHP_REDIS_API int
redis_xinfo_reply(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1799 redis_xinfo_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
1800 {
1801     zval z_ret;
1802     int elements;
1803 
1804     if (read_mbulk_header(redis_sock, &elements) == SUCCESS) {
1805         array_init(&z_ret);
1806         if (redis_read_xinfo_response(redis_sock, &z_ret, elements) == SUCCESS) {
1807             if (IS_ATOMIC(redis_sock)) {
1808                 RETVAL_ZVAL(&z_ret, 0, 1);
1809             } else {
1810                 add_next_index_zval(z_tab, &z_ret);
1811             }
1812             return SUCCESS;
1813         }
1814         zval_dtor(&z_ret);
1815     }
1816     if (IS_ATOMIC(redis_sock)) {
1817         RETVAL_FALSE;
1818     } else {
1819         add_next_index_bool(z_tab, 0);
1820     }
1821     return FAILURE;
1822 }
1823 
1824 PHP_REDIS_API int
redis_read_acl_log_reply(RedisSock * redis_sock,zval * zret,long count)1825 redis_read_acl_log_reply(RedisSock *redis_sock, zval *zret, long count) {
1826     zval zsub;
1827     int i, nsub;
1828 
1829     for (i = 0; i < count; i++) {
1830         if (read_mbulk_header(redis_sock, &nsub) < 0 || nsub % 2 != 0)
1831             return FAILURE;
1832 
1833         array_init(&zsub);
1834         if (redis_mbulk_reply_zipped_raw_variant(redis_sock, &zsub, nsub) == FAILURE)
1835             return FAILURE;
1836 
1837         add_next_index_zval(zret, &zsub);
1838     }
1839 
1840     return SUCCESS;
1841 }
1842 
1843 PHP_REDIS_API int
redis_read_acl_getuser_reply(RedisSock * redis_sock,zval * zret,long count)1844 redis_read_acl_getuser_reply(RedisSock *redis_sock, zval *zret, long count) {
1845     REDIS_REPLY_TYPE type;
1846     zval zv;
1847     char *key, *val;
1848     long vlen;
1849     int klen, i;
1850 
1851     for (i = 0; i < count; i += 2) {
1852         if (!(key = redis_sock_read(redis_sock, &klen)) ||
1853             redis_read_reply_type(redis_sock, &type, &vlen) < 0 ||
1854             (type != TYPE_BULK && type != TYPE_MULTIBULK) ||
1855             vlen > INT_MAX)
1856         {
1857             if (key) efree(key);
1858             return FAILURE;
1859         }
1860 
1861         if (type == TYPE_BULK) {
1862             if (!(val = redis_sock_read_bulk_reply(redis_sock, (int)vlen)))
1863                 return FAILURE;
1864             add_assoc_stringl_ex(zret, key, klen, val, vlen);
1865             efree(val);
1866         } else {
1867             array_init(&zv);
1868             redis_mbulk_reply_loop(redis_sock, &zv, (int)vlen, UNSERIALIZE_NONE);
1869             add_assoc_zval_ex(zret, key, klen, &zv);
1870         }
1871 
1872         efree(key);
1873     }
1874 
1875     return SUCCESS;
1876 }
1877 
redis_acl_custom_reply(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx,int (* cb)(RedisSock *,zval *,long))1878 int redis_acl_custom_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx,
1879                            int (*cb)(RedisSock*, zval*, long)) {
1880     REDIS_REPLY_TYPE type;
1881     int res = FAILURE;
1882     zval zret;
1883     long len;
1884 
1885     if (redis_read_reply_type(redis_sock, &type, &len) == 0 && type == TYPE_MULTIBULK) {
1886         array_init(&zret);
1887 
1888         res = cb(redis_sock, &zret, len);
1889         if (res == FAILURE) {
1890             zval_dtor(&zret);
1891             ZVAL_FALSE(&zret);
1892         }
1893     } else {
1894         ZVAL_FALSE(&zret);
1895     }
1896 
1897     if (IS_ATOMIC(redis_sock)) {
1898         RETVAL_ZVAL(&zret, 0, 0);
1899     } else {
1900         add_next_index_zval(z_tab, &zret);
1901     }
1902 
1903     return res;
1904 }
1905 
redis_acl_getuser_reply(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1906 int redis_acl_getuser_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
1907     return redis_acl_custom_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, ctx,
1908                                   redis_read_acl_getuser_reply);
1909 }
1910 
redis_acl_log_reply(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1911 int redis_acl_log_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
1912     return redis_acl_custom_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, ctx,
1913                                   redis_read_acl_log_reply);
1914 }
1915 
1916 /* Zipped key => value reply but we don't touch anything (e.g. CONFIG GET) */
redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1917 PHP_REDIS_API int redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
1918 {
1919     return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
1920         z_tab, UNSERIALIZE_NONE, SCORE_DECODE_NONE);
1921 }
1922 
1923 /* Zipped key => value reply unserializing keys and decoding the score as an integer (PUBSUB) */
redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1924 PHP_REDIS_API int redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1925                                                zval *z_tab, void *ctx)
1926 {
1927     return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
1928         z_tab, UNSERIALIZE_KEYS, SCORE_DECODE_INT);
1929 }
1930 
1931 /* Zipped key => value reply unserializing keys and decoding the score as a double (ZSET commands) */
redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1932 PHP_REDIS_API int redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1933                                                     zval *z_tab, void *ctx)
1934 {
1935     return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
1936         z_tab, UNSERIALIZE_KEYS, SCORE_DECODE_DOUBLE);
1937 }
1938 
1939 /* Zipped key => value reply where only the values are unserialized (e.g. HMGET) */
redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1940 PHP_REDIS_API int redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1941                                                zval *z_tab, void *ctx)
1942 {
1943     return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
1944         z_tab, UNSERIALIZE_VALS, SCORE_DECODE_NONE);
1945 }
1946 
1947 PHP_REDIS_API int
redis_1_response(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1948 redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
1949 {
1950     char *response;
1951     int response_len;
1952     zend_bool ret = 0;
1953 
1954     if ((response = redis_sock_read(redis_sock, &response_len)) != NULL) {
1955         ret = (response[1] == '1');
1956         efree(response);
1957     }
1958 
1959     if (IS_ATOMIC(redis_sock)) {
1960         RETVAL_BOOL(ret);
1961     } else {
1962         add_next_index_bool(z_tab, ret);
1963     }
1964 
1965     return ret ? SUCCESS : FAILURE;
1966 }
1967 
redis_string_response(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)1968 PHP_REDIS_API int redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
1969 
1970     char *response;
1971     int response_len;
1972 
1973     if ((response = redis_sock_read(redis_sock, &response_len))
1974                                     == NULL)
1975     {
1976         if (IS_ATOMIC(redis_sock)) {
1977             RETVAL_FALSE;
1978         } else {
1979             add_next_index_bool(z_tab, 0);
1980         }
1981         return FAILURE;
1982     }
1983     if (IS_ATOMIC(redis_sock)) {
1984         if (!redis_unpack(redis_sock, response, response_len, return_value)) {
1985             RETVAL_STRINGL(response, response_len);
1986         }
1987     } else {
1988         zval z_unpacked;
1989         if (redis_unpack(redis_sock, response, response_len, &z_unpacked)) {
1990             add_next_index_zval(z_tab, &z_unpacked);
1991         } else {
1992             add_next_index_stringl(z_tab, response, response_len);
1993         }
1994     }
1995 
1996     efree(response);
1997     return SUCCESS;
1998 }
1999 
2000 PHP_REDIS_API
redis_single_line_reply(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)2001 void redis_single_line_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2002                              zval *z_tab, void *ctx)
2003 {
2004     char buffer[4096];
2005     size_t len;
2006 
2007     if (redis_sock_read_single_line(redis_sock, buffer, sizeof(buffer), &len, 1) < 0) {
2008         if (IS_ATOMIC(redis_sock)) {
2009             RETURN_FALSE;
2010         } else {
2011             add_next_index_bool(z_tab, 0);
2012         }
2013         return;
2014     }
2015 
2016     //str = estrndup(buffer, len);
2017     if (IS_ATOMIC(redis_sock)) {
2018         RETVAL_STRINGL(buffer, len);
2019     } else {
2020         add_next_index_stringl(z_tab, buffer, len);
2021     }
2022 }
2023 
2024 /* like string response, but never unserialized. */
2025 PHP_REDIS_API int
redis_ping_response(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)2026 redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2027                     zval *z_tab, void *ctx)
2028 {
2029 
2030     char *response;
2031     int response_len;
2032 
2033     if ((response = redis_sock_read(redis_sock, &response_len))
2034                                     == NULL)
2035     {
2036         if (IS_ATOMIC(redis_sock)) {
2037             RETVAL_FALSE;
2038         } else {
2039             add_next_index_bool(z_tab, 0);
2040         }
2041         return FAILURE;
2042     }
2043     if (IS_ATOMIC(redis_sock)) {
2044         RETVAL_STRINGL(response, response_len);
2045     } else {
2046         add_next_index_stringl(z_tab, response, response_len);
2047     }
2048 
2049     efree(response);
2050     return SUCCESS;
2051 }
2052 
2053 /* Response for DEBUG object which is a formatted single line reply */
redis_debug_response(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)2054 PHP_REDIS_API void redis_debug_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
2055                                         zval *z_tab, void *ctx)
2056 {
2057     char *resp, *p, *p2, *p3, *p4;
2058     int is_numeric,  resp_len;
2059 
2060     /* Add or return false if we can't read from the socket */
2061     if((resp = redis_sock_read(redis_sock, &resp_len))==NULL) {
2062         if (IS_ATOMIC(redis_sock)) {
2063             RETURN_FALSE;
2064         }
2065         add_next_index_bool(z_tab, 0);
2066         return;
2067     }
2068 
2069     zval z_result;
2070     array_init(&z_result);
2071 
2072     /* Skip the '+' */
2073     p = resp + 1;
2074 
2075     /* <info>:<value> <info2:value2> ... */
2076     while((p2 = strchr(p, ':'))!=NULL) {
2077         /* Null terminate at the ':' */
2078         *p2++ = '\0';
2079 
2080         /* Null terminate at the space if we have one */
2081         if((p3 = strchr(p2, ' '))!=NULL) {
2082             *p3++ = '\0';
2083         } else {
2084             p3 = resp + resp_len;
2085         }
2086 
2087         is_numeric = 1;
2088         for(p4=p2; *p4; ++p4) {
2089             if(*p4 < '0' || *p4 > '9') {
2090                 is_numeric = 0;
2091                 break;
2092             }
2093         }
2094 
2095         /* Add our value */
2096         if(is_numeric) {
2097             add_assoc_long(&z_result, p, atol(p2));
2098         } else {
2099             add_assoc_string(&z_result, p, p2);
2100         }
2101 
2102         p = p3;
2103     }
2104 
2105     efree(resp);
2106 
2107     if (IS_ATOMIC(redis_sock)) {
2108         RETVAL_ZVAL(&z_result, 0, 1);
2109     } else {
2110         add_next_index_zval(z_tab, &z_result);
2111     }
2112 }
2113 
2114 /**
2115  * redis_sock_create
2116  */
2117 PHP_REDIS_API RedisSock*
redis_sock_create(char * host,int host_len,int port,double timeout,double read_timeout,int persistent,char * persistent_id,long retry_interval)2118 redis_sock_create(char *host, int host_len, int port,
2119                   double timeout, double read_timeout,
2120                   int persistent, char *persistent_id,
2121                   long retry_interval)
2122 {
2123     RedisSock *redis_sock;
2124 
2125     redis_sock = ecalloc(1, sizeof(RedisSock));
2126     redis_sock->host = zend_string_init(host, host_len, 0);
2127     redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED;
2128     redis_sock->retry_interval = retry_interval * 1000;
2129     redis_sock->max_retries = 10;
2130     redis_initialize_backoff(&redis_sock->backoff, retry_interval);
2131     redis_sock->persistent = persistent;
2132 
2133     if (persistent && persistent_id != NULL) {
2134         redis_sock->persistent_id = zend_string_init(persistent_id, strlen(persistent_id), 0);
2135     }
2136 
2137     redis_sock->port    = port;
2138     redis_sock->timeout = timeout;
2139     redis_sock->read_timeout = read_timeout;
2140 
2141     redis_sock->serializer = REDIS_SERIALIZER_NONE;
2142     redis_sock->compression = REDIS_COMPRESSION_NONE;
2143     redis_sock->mode = ATOMIC;
2144 
2145     return redis_sock;
2146 }
2147 
redis_uniqid(char * buf,size_t buflen)2148 static int redis_uniqid(char *buf, size_t buflen) {
2149     struct timeval tv;
2150     gettimeofday(&tv, NULL);
2151 
2152     return snprintf(buf, buflen, "phpredis:%08lx%05lx:%08lx",
2153                     (long)tv.tv_sec, (long)tv.tv_usec, (long)php_rand());
2154 }
2155 
redis_stream_liveness_check(php_stream * stream)2156 static int redis_stream_liveness_check(php_stream *stream) {
2157     return php_stream_set_option(stream, PHP_STREAM_OPTION_CHECK_LIVENESS,
2158                                  0, NULL) == PHP_STREAM_OPTION_RETURN_OK ?
2159                                  SUCCESS : FAILURE;
2160 }
2161 
2162 /* Try to get the underlying socket FD for use with poll/select.
2163  * Returns -1 on failure. */
redis_stream_fd_for_select(php_stream * stream)2164 static php_socket_t redis_stream_fd_for_select(php_stream *stream) {
2165     php_socket_t fd;
2166     int flags;
2167 
2168     flags = PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL;
2169     if (php_stream_cast(stream, flags, (void*)&fd, 1) == FAILURE)
2170         return -1;
2171 
2172     return fd;
2173 }
2174 
redis_detect_dirty_config(void)2175 static int redis_detect_dirty_config(void) {
2176     int val = INI_INT("redis.pconnect.pool_detect_dirty");
2177 
2178     if (val >= 0 && val <= 2)
2179         return val;
2180     else if (val > 2)
2181         return 2;
2182     else
2183         return 0;
2184 }
2185 
redis_pool_poll_timeout(void)2186 static int redis_pool_poll_timeout(void) {
2187     int val = INI_INT("redis.pconnect.pool_poll_timeout");
2188     if (val >= 0)
2189         return val;
2190 
2191     return 0;
2192 }
2193 
2194 #define REDIS_POLL_FD_SET(_pfd, _fd, _events) \
2195     (_pfd).fd = _fd; (_pfd).events = _events; (_pfd).revents = 0
2196 
2197 /* Try to determine if the socket is out of sync (has unconsumed replies) */
redis_stream_detect_dirty(php_stream * stream)2198 static int redis_stream_detect_dirty(php_stream *stream) {
2199     php_socket_t fd;
2200     php_pollfd pfd;
2201     int rv, action;
2202 
2203     /* Short circuit if this is disabled */
2204     if ((action = redis_detect_dirty_config()) == 0)
2205         return SUCCESS;
2206 
2207     /* Seek past unconsumed bytes if we detect them */
2208     if (stream->readpos < stream->writepos) {
2209         redisDbgFmt("%s on unconsumed buffer (%ld < %ld)",
2210                     action > 1 ? "Aborting" : "Seeking",
2211                     (long)stream->readpos, (long)stream->writepos);
2212 
2213         /* Abort if we are configured to immediately fail */
2214         if (action == 1)
2215             return FAILURE;
2216 
2217         /* Seek to the end of buffered data */
2218         zend_off_t offset = stream->writepos - stream->readpos;
2219         if (php_stream_seek(stream, offset, SEEK_CUR) == FAILURE)
2220             return FAILURE;
2221     }
2222 
2223     /* Get the underlying FD */
2224     if ((fd = redis_stream_fd_for_select(stream)) == -1)
2225         return FAILURE;
2226 
2227     /* We want to detect a readable socket (it shouln't be) */
2228     REDIS_POLL_FD_SET(pfd, fd, PHP_POLLREADABLE);
2229     rv = php_poll2(&pfd, 1, redis_pool_poll_timeout());
2230 
2231     /* If we detect the socket is readable, it's dirty which is
2232      * a failure.  Otherwise as best we can tell it's good.
2233      * TODO:  We could attempt to consume up to N bytes */
2234     redisDbgFmt("Detected %s socket", rv > 0 ? "readable" : "unreadable");
2235     return rv == 0 ? SUCCESS : FAILURE;
2236 }
2237 
2238 static int
redis_sock_check_liveness(RedisSock * redis_sock)2239 redis_sock_check_liveness(RedisSock *redis_sock)
2240 {
2241     char id[64], inbuf[4096];
2242     int idlen, auth;
2243     smart_string cmd = {0};
2244     size_t len;
2245 
2246     /* Short circuit if PHP detects the stream isn't live */
2247     if (redis_stream_liveness_check(redis_sock->stream) != SUCCESS)
2248         goto failure;
2249 
2250     /* Short circuit if we detect the stream is "dirty", can't or are
2251        configured not to try and fix it */
2252     if (redis_stream_detect_dirty(redis_sock->stream) != SUCCESS)
2253         goto failure;
2254 
2255     redis_sock->status = REDIS_SOCK_STATUS_CONNECTED;
2256     if (!INI_INT("redis.pconnect.echo_check_liveness")) {
2257         return SUCCESS;
2258     }
2259 
2260     /* AUTH (if we need it) */
2261     auth = redis_sock_append_auth(redis_sock, &cmd);
2262 
2263     /* ECHO challenge/response */
2264     idlen = redis_uniqid(id, sizeof(id));
2265     REDIS_CMD_INIT_SSTR_STATIC(&cmd, 1, "ECHO");
2266     redis_cmd_append_sstr(&cmd, id, idlen);
2267 
2268     /* Send command(s) and make sure we can consume reply(ies) */
2269     if (redis_sock_write(redis_sock, cmd.c, cmd.len) < 0) {
2270         smart_string_free(&cmd);
2271         goto failure;
2272     }
2273     smart_string_free(&cmd);
2274 
2275     if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
2276         goto failure;
2277     }
2278 
2279     if (auth) {
2280         if (strncmp(inbuf, "+OK", 3) == 0 || strncmp(inbuf, "-ERR Client sent AUTH", 21) == 0) {
2281             /* successfully authenticated or authentication isn't required */
2282             if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
2283                 goto failure;
2284             }
2285         } else if (strncmp(inbuf, "-NOAUTH", 7) == 0) {
2286             /* connection is fine but authentication failed, next command must fails too */
2287             if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0 || strncmp(inbuf, "-NOAUTH", 7) != 0) {
2288                 goto failure;
2289             }
2290             return SUCCESS;
2291         } else {
2292             goto failure;
2293         }
2294         redis_sock->status = REDIS_SOCK_STATUS_READY;
2295     } else {
2296         if (strncmp(inbuf, "-NOAUTH", 7) == 0) {
2297             /* connection is fine but authentication required */
2298             return SUCCESS;
2299         }
2300     }
2301 
2302     /* check echo response */
2303     if (*inbuf != TYPE_BULK || atoi(inbuf + 1) != idlen ||
2304         redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0 ||
2305         strncmp(inbuf, id, idlen) != 0
2306     ) {
2307         goto failure;
2308     }
2309 
2310     return SUCCESS;
2311 failure:
2312     redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED;
2313     if (redis_sock->stream) {
2314         php_stream_pclose(redis_sock->stream);
2315         redis_sock->stream = NULL;
2316     }
2317     return FAILURE;
2318 }
2319 
2320 /**
2321  * redis_sock_connect
2322  */
redis_sock_connect(RedisSock * redis_sock)2323 PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock)
2324 {
2325     struct timeval tv, read_tv, *tv_ptr = NULL;
2326     zend_string *persistent_id = NULL, *estr = NULL;
2327     char host[1024], *pos, *address, *scheme = NULL;
2328     const char *fmtstr = "%s://%s:%d";
2329     int host_len, usocket = 0, err = 0, tcp_flag = 1, scheme_free = 0;
2330     ConnectionPool *p = NULL;
2331 
2332     if (redis_sock->stream != NULL) {
2333         redis_sock_disconnect(redis_sock, 0);
2334     }
2335 
2336     address = ZSTR_VAL(redis_sock->host);
2337     if ((pos = strstr(address, "://")) == NULL) {
2338         scheme = redis_sock->stream_ctx ? "ssl" : "tcp";
2339     } else {
2340         scheme = estrndup(address, pos - address);
2341         address = pos + sizeof("://") - 1;
2342         scheme_free = 1;
2343     }
2344     if (address[0] == '/' && redis_sock->port < 1) {
2345         host_len = snprintf(host, sizeof(host), "unix://%s", address);
2346         usocket = 1;
2347     } else {
2348         if(redis_sock->port == 0)
2349             redis_sock->port = 6379;
2350 
2351 #ifdef HAVE_IPV6
2352         /* If we've got IPv6 and find a colon in our address, convert to proper
2353          * IPv6 [host]:port format */
2354         if (strchr(address, ':') != NULL) {
2355             fmtstr = "%s://[%s]:%d";
2356         }
2357 #endif
2358         host_len = snprintf(host, sizeof(host), fmtstr, scheme, address, redis_sock->port);
2359     }
2360     if (scheme_free) efree(scheme);
2361 
2362     if (redis_sock->persistent) {
2363         if (INI_INT("redis.pconnect.pooling_enabled")) {
2364             p = redis_sock_get_connection_pool(redis_sock);
2365             if (zend_llist_count(&p->list) > 0) {
2366                 redis_sock->stream = *(php_stream **)zend_llist_get_last(&p->list);
2367                 zend_llist_remove_tail(&p->list);
2368 
2369                 if (redis_sock_check_liveness(redis_sock) == SUCCESS) {
2370                     return SUCCESS;
2371                 }
2372 
2373                 p->nb_active--;
2374             }
2375 
2376             int limit = INI_INT("redis.pconnect.connection_limit");
2377             if (limit > 0 && p->nb_active >= limit) {
2378                 redis_sock_set_err(redis_sock, "Connection limit reached", sizeof("Connection limit reached") - 1);
2379                 return FAILURE;
2380             }
2381 
2382             gettimeofday(&tv, NULL);
2383             persistent_id = strpprintf(0, "phpredis_%ld%ld", tv.tv_sec, tv.tv_usec);
2384         } else {
2385             if (redis_sock->persistent_id) {
2386                 persistent_id = strpprintf(0, "phpredis:%s:%s", host, ZSTR_VAL(redis_sock->persistent_id));
2387             } else {
2388                 persistent_id = strpprintf(0, "phpredis:%s:%f", host, redis_sock->timeout);
2389             }
2390         }
2391     }
2392 
2393     tv.tv_sec  = (time_t)redis_sock->timeout;
2394     tv.tv_usec = (int)((redis_sock->timeout - tv.tv_sec) * 1000000);
2395     if (tv.tv_sec != 0 || tv.tv_usec != 0) {
2396         tv_ptr = &tv;
2397     }
2398 
2399     redis_sock->stream = php_stream_xport_create(host, host_len,
2400         0, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT,
2401         persistent_id ? ZSTR_VAL(persistent_id) : NULL,
2402         tv_ptr, redis_sock->stream_ctx, &estr, &err);
2403 
2404     if (persistent_id) {
2405         zend_string_release(persistent_id);
2406     }
2407 
2408     if (!redis_sock->stream) {
2409         if (estr) {
2410             redis_sock_set_err(redis_sock, ZSTR_VAL(estr), ZSTR_LEN(estr));
2411             zend_string_release(estr);
2412         }
2413         return FAILURE;
2414     }
2415 
2416     if (p) p->nb_active++;
2417 
2418     /* Attempt to set TCP_NODELAY/TCP_KEEPALIVE if we're not using a unix socket. */
2419     if (!usocket) {
2420         php_netstream_data_t *sock = (php_netstream_data_t*)redis_sock->stream->abstract;
2421         err = setsockopt(sock->socket, IPPROTO_TCP, TCP_NODELAY, (char*) &tcp_flag, sizeof(tcp_flag));
2422         PHPREDIS_NOTUSED(err);
2423         err = setsockopt(sock->socket, SOL_SOCKET, SO_KEEPALIVE, (char*) &redis_sock->tcp_keepalive, sizeof(redis_sock->tcp_keepalive));
2424         PHPREDIS_NOTUSED(err);
2425     }
2426 
2427     php_stream_auto_cleanup(redis_sock->stream);
2428 
2429     read_tv.tv_sec  = (time_t)redis_sock->read_timeout;
2430     read_tv.tv_usec = (int)((redis_sock->read_timeout - read_tv.tv_sec) * 1000000);
2431 
2432     if (read_tv.tv_sec != 0 || read_tv.tv_usec != 0) {
2433         php_stream_set_option(redis_sock->stream,PHP_STREAM_OPTION_READ_TIMEOUT,
2434             0, &read_tv);
2435     }
2436     php_stream_set_option(redis_sock->stream,
2437         PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_NONE, NULL);
2438 
2439     redis_sock->status = REDIS_SOCK_STATUS_CONNECTED;
2440 
2441     return SUCCESS;
2442 }
2443 
2444 /**
2445  * redis_sock_server_open
2446  */
2447 PHP_REDIS_API int
redis_sock_server_open(RedisSock * redis_sock)2448 redis_sock_server_open(RedisSock *redis_sock)
2449 {
2450     if (redis_sock) {
2451         switch (redis_sock->status) {
2452         case REDIS_SOCK_STATUS_DISCONNECTED:
2453             if (redis_sock_connect(redis_sock) != SUCCESS) {
2454                 break;
2455             } else if (redis_sock->status == REDIS_SOCK_STATUS_READY) {
2456                 return SUCCESS;
2457             }
2458             // fall through
2459         case REDIS_SOCK_STATUS_CONNECTED:
2460             if (redis_sock_auth(redis_sock) != SUCCESS)
2461                 break;
2462             redis_sock->status = REDIS_SOCK_STATUS_READY;
2463             // fall through
2464         case REDIS_SOCK_STATUS_READY:
2465             return SUCCESS;
2466         default:
2467             return FAILURE;
2468         }
2469     }
2470     return FAILURE;
2471 }
2472 
2473 /**
2474  * redis_sock_disconnect
2475  */
2476 PHP_REDIS_API int
redis_sock_disconnect(RedisSock * redis_sock,int force)2477 redis_sock_disconnect(RedisSock *redis_sock, int force)
2478 {
2479     if (redis_sock == NULL) {
2480         return FAILURE;
2481     } else if (redis_sock->stream) {
2482         if (redis_sock->persistent) {
2483             ConnectionPool *p = NULL;
2484             if (INI_INT("redis.pconnect.pooling_enabled")) {
2485                 p = redis_sock_get_connection_pool(redis_sock);
2486             }
2487             if (force) {
2488                 php_stream_pclose(redis_sock->stream);
2489                 if (p) p->nb_active--;
2490             } else if (p) {
2491                 zend_llist_prepend_element(&p->list, &redis_sock->stream);
2492             }
2493         } else {
2494             php_stream_close(redis_sock->stream);
2495         }
2496         redis_sock->stream = NULL;
2497     }
2498     redis_sock->mode = ATOMIC;
2499     redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED;
2500     redis_sock->watching = 0;
2501 
2502     return SUCCESS;
2503 }
2504 
2505 /**
2506  * redis_sock_set_err
2507  */
2508 PHP_REDIS_API void
redis_sock_set_err(RedisSock * redis_sock,const char * msg,int msg_len)2509 redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len)
2510 {
2511     // Free our last error
2512     if (redis_sock->err != NULL) {
2513         zend_string_release(redis_sock->err);
2514         redis_sock->err = NULL;
2515     }
2516 
2517     if (msg != NULL && msg_len > 0) {
2518         // Copy in our new error message
2519         redis_sock->err = zend_string_init(msg, msg_len, 0);
2520     }
2521 }
2522 
2523 PHP_REDIS_API int
redis_sock_set_stream_context(RedisSock * redis_sock,zval * options)2524 redis_sock_set_stream_context(RedisSock *redis_sock, zval *options)
2525 {
2526     zend_string *zkey;
2527     zval *z_ele;
2528 
2529     if (!redis_sock || Z_TYPE_P(options) != IS_ARRAY)
2530         return FAILURE;
2531 
2532     if (!redis_sock->stream_ctx)
2533         redis_sock->stream_ctx = php_stream_context_alloc();
2534 
2535     ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(options), zkey, z_ele) {
2536         php_stream_context_set_option(redis_sock->stream_ctx, "ssl", ZSTR_VAL(zkey), z_ele);
2537     } ZEND_HASH_FOREACH_END();
2538 
2539     return SUCCESS;
2540 }
2541 
2542 /**
2543  * redis_sock_read_multibulk_reply
2544  */
redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)2545 PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS,
2546                                            RedisSock *redis_sock, zval *z_tab,
2547                                            void *ctx)
2548 {
2549     zval z_multi_result;
2550     char inbuf[4096];
2551     int numElems;
2552     size_t len;
2553 
2554     if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
2555         return -1;
2556     }
2557 
2558     if(inbuf[0] != '*') {
2559         if (IS_ATOMIC(redis_sock)) {
2560             if (inbuf[0] == '-') {
2561                 redis_sock_set_err(redis_sock, inbuf+1, len);
2562             }
2563             RETVAL_FALSE;
2564         } else {
2565             add_next_index_bool(z_tab, 0);
2566         }
2567         return -1;
2568     }
2569 
2570     numElems = atoi(inbuf+1);
2571 
2572     if (numElems == -1 && redis_sock->null_mbulk_as_null) {
2573         ZVAL_NULL(&z_multi_result);
2574     } else {
2575         array_init(&z_multi_result);
2576         redis_mbulk_reply_loop(redis_sock, &z_multi_result, numElems, UNSERIALIZE_ALL);
2577     }
2578 
2579     if (IS_ATOMIC(redis_sock)) {
2580         RETVAL_ZVAL(&z_multi_result, 0, 1);
2581     } else {
2582         add_next_index_zval(z_tab, &z_multi_result);
2583     }
2584 
2585     return 0;
2586 }
2587 
2588 /* Like multibulk reply, but don't touch the values, they won't be unserialized
2589  * (this is used by HKEYS). */
2590 PHP_REDIS_API int
redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)2591 redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
2592 {
2593     char inbuf[4096];
2594     int numElems;
2595     size_t len;
2596 
2597     if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
2598         return -1;
2599     }
2600 
2601     if(inbuf[0] != '*') {
2602         if (IS_ATOMIC(redis_sock)) {
2603             if (inbuf[0] == '-') {
2604                 redis_sock_set_err(redis_sock, inbuf+1, len);
2605             }
2606             RETVAL_FALSE;
2607         } else {
2608             add_next_index_bool(z_tab, 0);
2609         }
2610         return -1;
2611     }
2612     numElems = atoi(inbuf+1);
2613     zval z_multi_result;
2614     array_init(&z_multi_result); /* pre-allocate array for multi's results. */
2615 
2616     redis_mbulk_reply_loop(redis_sock, &z_multi_result, numElems, UNSERIALIZE_NONE);
2617 
2618     if (IS_ATOMIC(redis_sock)) {
2619         RETVAL_ZVAL(&z_multi_result, 0, 1);
2620     } else {
2621         add_next_index_zval(z_tab, &z_multi_result);
2622     }
2623 
2624     return 0;
2625 }
2626 
2627 PHP_REDIS_API void
redis_mbulk_reply_loop(RedisSock * redis_sock,zval * z_tab,int count,int unserialize)2628 redis_mbulk_reply_loop(RedisSock *redis_sock, zval *z_tab, int count,
2629                        int unserialize)
2630 {
2631     zval z_unpacked;
2632     char *line;
2633     int i, len;
2634 
2635     for (i = 0; i < count; ++i) {
2636         if ((line = redis_sock_read(redis_sock, &len)) == NULL) {
2637             add_next_index_bool(z_tab, 0);
2638             continue;
2639         }
2640 
2641         /* We will attempt unserialization, if we're unserializing everything,
2642          * or if we're unserializing keys and we're on a key, or we're
2643          * unserializing values and we're on a value! */
2644         int unwrap = (
2645             (unserialize == UNSERIALIZE_ALL) ||
2646             (unserialize == UNSERIALIZE_KEYS && i % 2 == 0) ||
2647             (unserialize == UNSERIALIZE_VALS && i % 2 != 0)
2648         );
2649 
2650         if (unwrap && redis_unpack(redis_sock, line, len, &z_unpacked)) {
2651             add_next_index_zval(z_tab, &z_unpacked);
2652         } else {
2653             add_next_index_stringl(z_tab, line, len);
2654         }
2655         efree(line);
2656     }
2657 }
2658 
2659 static int
redis_mbulk_reply_zipped_raw_variant(RedisSock * redis_sock,zval * zret,int count)2660 redis_mbulk_reply_zipped_raw_variant(RedisSock *redis_sock, zval *zret, int count) {
2661     REDIS_REPLY_TYPE type;
2662     char *key, *val;
2663     int keylen, i;
2664     zend_long lval;
2665     double dval;
2666     long vallen;
2667 
2668     for (i = 0; i < count; i+= 2) {
2669         /* Keys should always be bulk strings */
2670         if ((key = redis_sock_read(redis_sock, &keylen)) == NULL)
2671             return FAILURE;
2672 
2673         /* This can vary */
2674         if (redis_read_reply_type(redis_sock, &type, &vallen) < 0)
2675             return FAILURE;
2676 
2677         if (type == TYPE_BULK) {
2678             if (vallen > INT_MAX || (val = redis_sock_read_bulk_reply(redis_sock, (int)vallen)) == NULL) {
2679                 efree(key);
2680                 return FAILURE;
2681             }
2682 
2683             /* Possibly overkill, but provides really nice types */
2684             switch (is_numeric_string(val, vallen, &lval, &dval, 0)) {
2685                 case IS_LONG:
2686                     add_assoc_long_ex(zret, key, keylen, lval);
2687                     break;
2688                 case IS_DOUBLE:
2689                     add_assoc_double_ex(zret, key, keylen, dval);
2690                     break;
2691                 default:
2692                     add_assoc_stringl_ex(zret, key, keylen, val, vallen);
2693             }
2694 
2695             efree(val);
2696         } else if (type == TYPE_INT) {
2697             add_assoc_long_ex(zret, key, keylen, (zend_long)vallen);
2698         } else {
2699             add_assoc_null_ex(zret, key, keylen);
2700         }
2701 
2702         efree(key);
2703     }
2704 
2705     return SUCCESS;
2706 }
2707 
2708 /* Specialized multibulk processing for HMGET where we need to pair requested
2709  * keys with their returned values */
redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)2710 PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
2711 {
2712     char inbuf[4096], *response;
2713     int response_len;
2714     int i, numElems;
2715     size_t len;
2716 
2717     zval *z_keys = ctx;
2718 
2719     if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) {
2720         goto failure;
2721     }
2722 
2723     if (*inbuf != TYPE_MULTIBULK) {
2724         if (IS_ATOMIC(redis_sock)) {
2725             RETVAL_FALSE;
2726         } else {
2727             add_next_index_bool(z_tab, 0);
2728         }
2729         if (*inbuf == TYPE_ERR) {
2730             redis_sock_set_err(redis_sock, inbuf + 1, len - 1);
2731         }
2732         goto failure;
2733     }
2734     numElems = atoi(inbuf+1);
2735     zval z_multi_result;
2736     array_init(&z_multi_result); /* pre-allocate array for multi's results. */
2737 
2738     for(i = 0; i < numElems; ++i) {
2739         zend_string *zstr = zval_get_string(&z_keys[i]);
2740         response = redis_sock_read(redis_sock, &response_len);
2741         if(response != NULL) {
2742             zval z_unpacked;
2743             if (redis_unpack(redis_sock, response, response_len, &z_unpacked)) {
2744                 add_assoc_zval_ex(&z_multi_result, ZSTR_VAL(zstr), ZSTR_LEN(zstr), &z_unpacked);
2745             } else {
2746                 add_assoc_stringl_ex(&z_multi_result, ZSTR_VAL(zstr), ZSTR_LEN(zstr), response, response_len);
2747             }
2748             efree(response);
2749         } else {
2750             add_assoc_bool_ex(&z_multi_result, ZSTR_VAL(zstr), ZSTR_LEN(zstr), 0);
2751         }
2752         zend_string_release(zstr);
2753         zval_dtor(&z_keys[i]);
2754     }
2755     efree(z_keys);
2756 
2757     if (IS_ATOMIC(redis_sock)) {
2758         RETVAL_ZVAL(&z_multi_result, 0, 1);
2759     } else {
2760         add_next_index_zval(z_tab, &z_multi_result);
2761     }
2762     return SUCCESS;
2763 failure:
2764     if (z_keys != NULL) {
2765         for (i = 0; Z_TYPE(z_keys[i]) != IS_NULL; ++i) {
2766             zval_dtor(&z_keys[i]);
2767         }
2768         efree(z_keys);
2769     }
2770     return FAILURE;
2771 }
2772 
2773 /**
2774  * redis_sock_write
2775  */
2776 PHP_REDIS_API int
redis_sock_write(RedisSock * redis_sock,char * cmd,size_t sz)2777 redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz)
2778 {
2779     if (redis_check_eof(redis_sock, 0) == 0 &&
2780         php_stream_write(redis_sock->stream, cmd, sz) == sz
2781     ) {
2782         return sz;
2783     }
2784     return -1;
2785 }
2786 
2787 /**
2788  * redis_free_socket
2789  */
redis_free_socket(RedisSock * redis_sock)2790 PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock)
2791 {
2792     if (redis_sock->prefix) {
2793         zend_string_release(redis_sock->prefix);
2794     }
2795     if (redis_sock->pipeline_cmd) {
2796         zend_string_release(redis_sock->pipeline_cmd);
2797     }
2798     if (redis_sock->err) {
2799         zend_string_release(redis_sock->err);
2800     }
2801     if (redis_sock->persistent_id) {
2802         zend_string_release(redis_sock->persistent_id);
2803     }
2804     if (redis_sock->host) {
2805         zend_string_release(redis_sock->host);
2806     }
2807     redis_sock_free_auth(redis_sock);
2808     efree(redis_sock);
2809 }
2810 
2811 #ifdef HAVE_REDIS_LZ4
2812 /* Implementation of CRC8 for our LZ4 checksum value */
crc8(unsigned char * input,size_t len)2813 static uint8_t crc8(unsigned char *input, size_t len) {
2814     size_t i;
2815     uint8_t crc = 0xFF;
2816 
2817     while (len--) {
2818         crc ^= *input++;
2819         for (i = 0; i < 8; i++) {
2820             if (crc & 0x80)
2821                 crc = (uint8_t)(crc << 1) ^ 0x31;
2822             else
2823                 crc <<= 1;
2824         }
2825     }
2826 
2827     return crc;
2828 }
2829 #endif
2830 
2831 PHP_REDIS_API int
redis_compress(RedisSock * redis_sock,char ** dst,size_t * dstlen,char * buf,size_t len)2832 redis_compress(RedisSock *redis_sock, char **dst, size_t *dstlen, char *buf, size_t len) {
2833     switch (redis_sock->compression) {
2834         case REDIS_COMPRESSION_LZF:
2835 #ifdef HAVE_REDIS_LZF
2836             {
2837                 char *data;
2838                 uint32_t res;
2839                 double size;
2840 
2841                 /* preserve compatibility with PECL lzf_compress margin (greater of 4% and LZF_MARGIN) */
2842                 size = len + MIN(UINT_MAX - len, MAX(LZF_MARGIN, len / 25));
2843                 data = emalloc(size);
2844                 if ((res = lzf_compress(buf, len, data, size)) > 0) {
2845                     *dst = data;
2846                     *dstlen = res;
2847                     return 1;
2848                 }
2849                 efree(data);
2850             }
2851 #endif
2852             break;
2853         case REDIS_COMPRESSION_ZSTD:
2854 #ifdef HAVE_REDIS_ZSTD
2855             {
2856                 char *data;
2857                 size_t size;
2858                 int level;
2859 
2860                 if (redis_sock->compression_level < 1) {
2861 #ifdef ZSTD_CLEVEL_DEFAULT
2862                     level = ZSTD_CLEVEL_DEFAULT;
2863 #else
2864                     level = 3;
2865 #endif
2866                 } else if (redis_sock->compression_level > ZSTD_maxCLevel()) {
2867                     level = ZSTD_maxCLevel();
2868                 } else {
2869                     level = redis_sock->compression_level;
2870                 }
2871 
2872                 size = ZSTD_compressBound(len);
2873                 data = emalloc(size);
2874                 size = ZSTD_compress(data, size, buf, len, level);
2875                 if (!ZSTD_isError(size)) {
2876                     *dst = erealloc(data, size);
2877                     *dstlen = size;
2878                     return 1;
2879                 }
2880                 efree(data);
2881             }
2882 #endif
2883             break;
2884         case REDIS_COMPRESSION_LZ4:
2885 #ifdef HAVE_REDIS_LZ4
2886             {
2887                 /* Compressing empty data is pointless */
2888                 if (len < 1)
2889                     break;
2890 
2891                 /* Compressing more than INT_MAX bytes would require multiple blocks */
2892                 if (len > INT_MAX) {
2893                     php_error_docref(NULL, E_WARNING,
2894                         "LZ4: compressing > %d bytes not supported", INT_MAX);
2895                     break;
2896                 }
2897 
2898                 int old_len = len, lz4len, lz4bound;
2899                 uint8_t crc = crc8((unsigned char*)&old_len, sizeof(old_len));
2900                 char *lz4buf, *lz4pos;
2901 
2902                 lz4bound = LZ4_compressBound(len);
2903                 lz4buf = emalloc(REDIS_LZ4_HDR_SIZE + lz4bound);
2904                 lz4pos = lz4buf;
2905 
2906                 /* Copy and move past crc8 length checksum */
2907                 memcpy(lz4pos, &crc, sizeof(crc));
2908                 lz4pos += sizeof(crc);
2909 
2910                 /* Copy and advance past length */
2911                 memcpy(lz4pos, &old_len, sizeof(old_len));
2912                 lz4pos += sizeof(old_len);
2913 
2914                 if (redis_sock->compression_level <= 0 || redis_sock->compression_level > REDIS_LZ4_MAX_CLEVEL) {
2915                     lz4len = LZ4_compress_default(buf, lz4pos, old_len, lz4bound);
2916                 } else {
2917                     lz4len = LZ4_compress_HC(buf, lz4pos, old_len, lz4bound, redis_sock->compression_level);
2918                 }
2919 
2920                 if (lz4len <= 0) {
2921                     efree(lz4buf);
2922                     break;
2923                 }
2924 
2925                 *dst = lz4buf;
2926                 *dstlen = lz4len + REDIS_LZ4_HDR_SIZE;
2927                 return 1;
2928             }
2929 #endif
2930             break;
2931     }
2932 
2933     *dst = buf;
2934     *dstlen = len;
2935     return 0;
2936 }
2937 
2938 PHP_REDIS_API int
redis_uncompress(RedisSock * redis_sock,char ** dst,size_t * dstlen,const char * src,size_t len)2939 redis_uncompress(RedisSock *redis_sock, char **dst, size_t *dstlen, const char *src, size_t len) {
2940     switch (redis_sock->compression) {
2941         case REDIS_COMPRESSION_LZF:
2942 #ifdef HAVE_REDIS_LZF
2943             {
2944                 char *data;
2945                 int i;
2946                 uint32_t res;
2947 
2948                 if (len == 0)
2949                     break;
2950 
2951                 /* start from two-times bigger buffer and
2952                  * increase it exponentially  if needed */
2953                 errno = E2BIG;
2954                 for (i = 2; errno == E2BIG; i *= 2) {
2955                     data = emalloc(i * len);
2956                     if ((res = lzf_decompress(src, len, data, i * len)) == 0) {
2957                         /* errno != E2BIG will brake for loop */
2958                         efree(data);
2959                         continue;
2960                     }
2961 
2962                     *dst = data;
2963                     *dstlen = res;
2964                     return 1;
2965                 }
2966 
2967                 efree(data);
2968                 break;
2969             }
2970 #endif
2971             break;
2972         case REDIS_COMPRESSION_ZSTD:
2973 #ifdef HAVE_REDIS_ZSTD
2974             {
2975                 char *data;
2976                 unsigned long long zlen;
2977 
2978                 zlen = ZSTD_getFrameContentSize(src, len);
2979                 if (zlen == ZSTD_CONTENTSIZE_ERROR || zlen == ZSTD_CONTENTSIZE_UNKNOWN || zlen > INT_MAX)
2980                     break;
2981 
2982                 data = emalloc(zlen);
2983                 *dstlen = ZSTD_decompress(data, zlen, src, len);
2984                 if (ZSTD_isError(*dstlen) || *dstlen != zlen) {
2985                     efree(data);
2986                     break;
2987                 }
2988 
2989                 *dst = data;
2990                 return 1;
2991             }
2992 #endif
2993             break;
2994         case REDIS_COMPRESSION_LZ4:
2995 #ifdef HAVE_REDIS_LZ4
2996             {
2997                 char *data;
2998                 int datalen;
2999                 uint8_t lz4crc;
3000 
3001                 /* We must have at least enough bytes for our header, and can't have more than
3002                  * INT_MAX + our header size. */
3003                 if (len < REDIS_LZ4_HDR_SIZE || len > INT_MAX + REDIS_LZ4_HDR_SIZE)
3004                     break;
3005 
3006                 /* Operate on copies in case our CRC fails */
3007                 const char *copy = src;
3008                 size_t copylen = len;
3009 
3010                 /* Read in our header bytes */
3011                 memcpy(&lz4crc, copy, sizeof(uint8_t));
3012                 copy += sizeof(uint8_t); copylen -= sizeof(uint8_t);
3013                 memcpy(&datalen, copy, sizeof(int));
3014                 copy += sizeof(int); copylen -= sizeof(int);
3015 
3016                 /* Make sure our CRC matches (TODO:  Maybe issue a docref error?) */
3017                 if (crc8((unsigned char*)&datalen, sizeof(datalen)) != lz4crc)
3018                     break;
3019 
3020                 /* Finally attempt decompression */
3021                 data = emalloc(datalen);
3022                 if (LZ4_decompress_safe(copy, data, copylen, datalen) > 0) {
3023                     *dst = data;
3024                     *dstlen = datalen;
3025                     return 1;
3026                 }
3027 
3028                 efree(data);
3029             }
3030 #endif
3031             break;
3032     }
3033 
3034     *dst = (char*)src;
3035     *dstlen = len;
3036     return 0;
3037 }
3038 
3039 PHP_REDIS_API int
redis_pack(RedisSock * redis_sock,zval * z,char ** val,size_t * val_len)3040 redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) {
3041     size_t tmplen;
3042     int tmpfree;
3043     char *tmp;
3044 
3045     /* First serialize */
3046     tmpfree = redis_serialize(redis_sock, z, &tmp, &tmplen);
3047 
3048     /* Now attempt compression */
3049     if (redis_compress(redis_sock, val, val_len, tmp, tmplen)) {
3050         if (tmpfree) efree(tmp);
3051         return 1;
3052     }
3053 
3054     return tmpfree;
3055 }
3056 
3057 PHP_REDIS_API int
redis_unpack(RedisSock * redis_sock,const char * src,int srclen,zval * zdst)3058 redis_unpack(RedisSock *redis_sock, const char *src, int srclen, zval *zdst) {
3059     size_t len;
3060     char *buf;
3061 
3062     /* Uncompress, then unserialize */
3063     if (redis_uncompress(redis_sock, &buf, &len, src, srclen)) {
3064         if (!redis_unserialize(redis_sock, buf, len, zdst)) {
3065             ZVAL_STRINGL(zdst, buf, len);
3066         }
3067         efree(buf);
3068         return 1;
3069     }
3070 
3071     return redis_unserialize(redis_sock, buf, len, zdst);
3072 }
3073 
3074 PHP_REDIS_API int
redis_serialize(RedisSock * redis_sock,zval * z,char ** val,size_t * val_len)3075 redis_serialize(RedisSock *redis_sock, zval *z, char **val, size_t *val_len)
3076 {
3077     php_serialize_data_t ht;
3078 
3079     smart_str sstr = {0};
3080 #ifdef HAVE_REDIS_IGBINARY
3081     size_t sz;
3082     uint8_t *val8;
3083 #endif
3084 
3085     *val = "";
3086     *val_len = 0;
3087     switch(redis_sock->serializer) {
3088         case REDIS_SERIALIZER_NONE:
3089             switch(Z_TYPE_P(z)) {
3090                 case IS_STRING:
3091                     *val = Z_STRVAL_P(z);
3092                     *val_len = Z_STRLEN_P(z);
3093                     break;
3094 
3095                 case IS_OBJECT:
3096                     *val = "Object";
3097                     *val_len = 6;
3098                     break;
3099 
3100                 case IS_ARRAY:
3101                     *val = "Array";
3102                     *val_len = 5;
3103                     break;
3104 
3105                 default: { /* copy */
3106                     zend_string *zstr = zval_get_string(z);
3107                     *val = estrndup(ZSTR_VAL(zstr), ZSTR_LEN(zstr));
3108                     *val_len = ZSTR_LEN(zstr);
3109                     zend_string_release(zstr);
3110                     return 1;
3111                 }
3112             }
3113             break;
3114         case REDIS_SERIALIZER_PHP:
3115             PHP_VAR_SERIALIZE_INIT(ht);
3116             php_var_serialize(&sstr, z, &ht);
3117 
3118             *val = estrndup(ZSTR_VAL(sstr.s), ZSTR_LEN(sstr.s));
3119             *val_len = ZSTR_LEN(sstr.s);
3120 
3121             smart_str_free(&sstr);
3122             PHP_VAR_SERIALIZE_DESTROY(ht);
3123 
3124             return 1;
3125 
3126         case REDIS_SERIALIZER_MSGPACK:
3127 #ifdef HAVE_REDIS_MSGPACK
3128             php_msgpack_serialize(&sstr, z);
3129             *val = estrndup(ZSTR_VAL(sstr.s), ZSTR_LEN(sstr.s));
3130             *val_len = ZSTR_LEN(sstr.s);
3131             smart_str_free(&sstr);
3132 
3133             return 1;
3134 #endif
3135             break;
3136         case REDIS_SERIALIZER_IGBINARY:
3137 #ifdef HAVE_REDIS_IGBINARY
3138             if(igbinary_serialize(&val8, (size_t *)&sz, z) == 0) {
3139                 *val = (char*)val8;
3140                 *val_len = sz;
3141                 return 1;
3142             }
3143 #endif
3144             break;
3145         case REDIS_SERIALIZER_JSON:
3146 #ifdef HAVE_REDIS_JSON
3147             php_json_encode(&sstr, z, PHP_JSON_OBJECT_AS_ARRAY);
3148             *val = estrndup(ZSTR_VAL(sstr.s), ZSTR_LEN(sstr.s));
3149             *val_len = ZSTR_LEN(sstr.s);
3150             smart_str_free(&sstr);
3151             return 1;
3152 #endif
3153             break;
3154         EMPTY_SWITCH_DEFAULT_CASE()
3155     }
3156 
3157     return 0;
3158 }
3159 
3160 PHP_REDIS_API int
redis_unserialize(RedisSock * redis_sock,const char * val,int val_len,zval * z_ret)3161 redis_unserialize(RedisSock* redis_sock, const char *val, int val_len,
3162                   zval *z_ret)
3163 {
3164 
3165     php_unserialize_data_t var_hash;
3166     int ret = 0;
3167 
3168     switch(redis_sock->serializer) {
3169         case REDIS_SERIALIZER_NONE:
3170             /* Nothing to do */
3171             break;
3172         case REDIS_SERIALIZER_PHP:
3173             PHP_VAR_UNSERIALIZE_INIT(var_hash);
3174 
3175             ret = php_var_unserialize(z_ret, (const unsigned char **)&val,
3176                                       (const unsigned char *)val + val_len,
3177                                        &var_hash);
3178 
3179             PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
3180             break;
3181 
3182         case REDIS_SERIALIZER_MSGPACK:
3183 #ifdef HAVE_REDIS_MSGPACK
3184             ret = !php_msgpack_unserialize(z_ret, (char *)val, (size_t)val_len);
3185 #endif
3186             break;
3187 
3188         case REDIS_SERIALIZER_IGBINARY:
3189 #ifdef HAVE_REDIS_IGBINARY
3190             /*
3191              * Check if the given string starts with an igbinary header.
3192              *
3193              * A modern igbinary string consists of the following format:
3194              *
3195              * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
3196              * | header (4) | type (1) | ... (n) |  NUL (1) |
3197              * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
3198              *
3199              * With header being either 0x00000001 or 0x00000002
3200              * (encoded as big endian).
3201              *
3202              * Not all versions contain the trailing NULL byte though, so
3203              * do not check for that.
3204              */
3205             if (val_len < 5
3206                     || (memcmp(val, "\x00\x00\x00\x01", 4) != 0
3207                     && memcmp(val, "\x00\x00\x00\x02", 4) != 0))
3208             {
3209                 /* This is most definitely not an igbinary string, so do
3210                    not try to unserialize this as one. */
3211                 break;
3212             }
3213 
3214             ret = !igbinary_unserialize((const uint8_t *)val, (size_t)val_len, z_ret);
3215 #endif
3216             break;
3217         case REDIS_SERIALIZER_JSON:
3218 #ifdef HAVE_REDIS_JSON
3219     #if (PHP_MAJOR_VERSION == 7 && PHP_MINOR_VERSION < 1)
3220             JSON_G(error_code) = PHP_JSON_ERROR_NONE;
3221             php_json_decode(z_ret, (char*)val, val_len, 1, PHP_JSON_PARSER_DEFAULT_DEPTH);
3222             ret = JSON_G(error_code) == PHP_JSON_ERROR_NONE;
3223     #else
3224             ret = !php_json_decode(z_ret, (char *)val, val_len, 1, PHP_JSON_PARSER_DEFAULT_DEPTH);
3225     #endif
3226 #endif
3227             break;
3228         EMPTY_SWITCH_DEFAULT_CASE()
3229     }
3230 
3231     return ret;
3232 }
3233 
3234 PHP_REDIS_API int
redis_key_prefix(RedisSock * redis_sock,char ** key,size_t * key_len)3235 redis_key_prefix(RedisSock *redis_sock, char **key, size_t *key_len) {
3236     int ret_len;
3237     char *ret;
3238 
3239     if (redis_sock->prefix == NULL) {
3240         return 0;
3241     }
3242 
3243     ret_len = ZSTR_LEN(redis_sock->prefix) + *key_len;
3244     ret = ecalloc(1 + ret_len, 1);
3245     memcpy(ret, ZSTR_VAL(redis_sock->prefix), ZSTR_LEN(redis_sock->prefix));
3246     memcpy(ret + ZSTR_LEN(redis_sock->prefix), *key, *key_len);
3247 
3248     *key = ret;
3249     *key_len = ret_len;
3250     return 1;
3251 }
3252 
3253 /*
3254  * Processing for variant reply types (think EVAL)
3255  */
3256 
3257 PHP_REDIS_API int
redis_sock_gets(RedisSock * redis_sock,char * buf,int buf_size,size_t * line_size)3258 redis_sock_gets(RedisSock *redis_sock, char *buf, int buf_size,
3259                 size_t *line_size)
3260 {
3261     // Handle EOF
3262     if(-1 == redis_check_eof(redis_sock, 0)) {
3263         return -1;
3264     }
3265 
3266     if(php_stream_get_line(redis_sock->stream, buf, buf_size, line_size)
3267                            == NULL)
3268     {
3269         char *errmsg = NULL;
3270 
3271         if (redis_sock->port < 0) {
3272             spprintf(&errmsg, 0, "read error on connection to %s", ZSTR_VAL(redis_sock->host));
3273         } else {
3274             spprintf(&errmsg, 0, "read error on connection to %s:%d", ZSTR_VAL(redis_sock->host), redis_sock->port);
3275         }
3276         // Close our socket
3277         redis_sock_disconnect(redis_sock, 1);
3278 
3279         // Throw a read error exception
3280         REDIS_THROW_EXCEPTION(errmsg, 0);
3281         efree(errmsg);
3282         return -1;
3283     }
3284 
3285     /* We don't need \r\n */
3286     *line_size-=2;
3287     buf[*line_size]='\0';
3288 
3289     /* Success! */
3290     return 0;
3291 }
3292 
3293 PHP_REDIS_API int
redis_read_reply_type(RedisSock * redis_sock,REDIS_REPLY_TYPE * reply_type,long * reply_info)3294 redis_read_reply_type(RedisSock *redis_sock, REDIS_REPLY_TYPE *reply_type,
3295                       long *reply_info)
3296 {
3297     // Make sure we haven't lost the connection, even trying to reconnect
3298     if(-1 == redis_check_eof(redis_sock, 0)) {
3299         // Failure
3300         *reply_type = EOF;
3301         return -1;
3302     }
3303 
3304     // Attempt to read the reply-type byte
3305     if((*reply_type = php_stream_getc(redis_sock->stream)) == EOF) {
3306         REDIS_THROW_EXCEPTION( "socket error on read socket", 0);
3307         return -1;
3308     }
3309 
3310     // If this is a BULK, MULTI BULK, or simply an INTEGER response, we can
3311     // extract the value or size info here
3312     if(*reply_type == TYPE_INT || *reply_type == TYPE_BULK ||
3313        *reply_type == TYPE_MULTIBULK)
3314     {
3315         // Buffer to hold size information
3316         char inbuf[255];
3317 
3318         /* Read up to our newline */
3319         if (php_stream_gets(redis_sock->stream, inbuf, sizeof(inbuf)) == NULL) {
3320             return -1;
3321         }
3322 
3323         /* Set our size response */
3324         *reply_info = atol(inbuf);
3325     }
3326 
3327     /* Success! */
3328     return 0;
3329 }
3330 
3331 /*
3332  * Read a single line response, having already consumed the reply-type byte
3333  */
3334 static int
redis_read_variant_line(RedisSock * redis_sock,REDIS_REPLY_TYPE reply_type,int as_string,zval * z_ret)3335 redis_read_variant_line(RedisSock *redis_sock, REDIS_REPLY_TYPE reply_type,
3336                         int as_string, zval *z_ret)
3337 {
3338     // Buffer to read our single line reply
3339     char inbuf[4096];
3340     size_t len;
3341 
3342     /* Attempt to read our single line reply */
3343     if(redis_sock_gets(redis_sock, inbuf, sizeof(inbuf), &len) < 0) {
3344         return -1;
3345     }
3346 
3347     /* Throw exception on SYNC error otherwise just set error string */
3348     if(reply_type == TYPE_ERR) {
3349         redis_sock_set_err(redis_sock, inbuf, len);
3350         redis_error_throw(redis_sock);
3351         ZVAL_FALSE(z_ret);
3352     } else if (as_string) {
3353         ZVAL_STRINGL(z_ret, inbuf, len);
3354     } else {
3355         ZVAL_TRUE(z_ret);
3356     }
3357 
3358     return 0;
3359 }
3360 
3361 PHP_REDIS_API int
redis_read_variant_bulk(RedisSock * redis_sock,int size,zval * z_ret)3362 redis_read_variant_bulk(RedisSock *redis_sock, int size, zval *z_ret
3363                        )
3364 {
3365     // Attempt to read the bulk reply
3366     char *bulk_resp = redis_sock_read_bulk_reply(redis_sock, size);
3367 
3368     /* Set our reply to FALSE on failure, and the string on success */
3369     if(bulk_resp == NULL) {
3370         ZVAL_FALSE(z_ret);
3371         return -1;
3372     }
3373     ZVAL_STRINGL(z_ret, bulk_resp, size);
3374     efree(bulk_resp);
3375     return 0;
3376 }
3377 
3378 PHP_REDIS_API int
redis_read_multibulk_recursive(RedisSock * redis_sock,long long elements,int status_strings,zval * z_ret)3379 redis_read_multibulk_recursive(RedisSock *redis_sock, long long elements, int status_strings,
3380                                zval *z_ret)
3381 {
3382     long reply_info;
3383     REDIS_REPLY_TYPE reply_type;
3384     zval z_subelem;
3385 
3386     // Iterate while we have elements
3387     while(elements > 0) {
3388         // Attempt to read our reply type
3389         if(redis_read_reply_type(redis_sock, &reply_type, &reply_info
3390                                 ) < 0)
3391         {
3392             zend_throw_exception_ex(redis_exception_ce, 0,
3393                 "protocol error, couldn't parse MULTI-BULK response\n");
3394             return FAILURE;
3395         }
3396 
3397         // Switch on our reply-type byte
3398         switch(reply_type) {
3399             case TYPE_ERR:
3400             case TYPE_LINE:
3401                 redis_read_variant_line(redis_sock, reply_type, status_strings,
3402                                         &z_subelem);
3403                 add_next_index_zval(z_ret, &z_subelem);
3404                 break;
3405             case TYPE_INT:
3406                 // Add our long value
3407                 add_next_index_long(z_ret, reply_info);
3408                 break;
3409             case TYPE_BULK:
3410                 // Init a zval for our bulk response, read and add it
3411                 redis_read_variant_bulk(redis_sock, reply_info, &z_subelem);
3412                 add_next_index_zval(z_ret, &z_subelem);
3413                 break;
3414             case TYPE_MULTIBULK:
3415                 if (reply_info < 0 && redis_sock->null_mbulk_as_null) {
3416                     add_next_index_null(z_ret);
3417                 } else {
3418                     array_init(&z_subelem);
3419                     if (reply_info > 0) {
3420                         redis_read_multibulk_recursive(redis_sock, reply_info, status_strings, &z_subelem);
3421                     }
3422                     add_next_index_zval(z_ret, &z_subelem);
3423                 }
3424                 break;
3425             default:
3426                 // Stop the compiler from whinging
3427                 break;
3428         }
3429 
3430         /* Decrement our element counter */
3431         elements--;
3432     }
3433 
3434     return 0;
3435 }
3436 
3437 static int
variant_reply_generic(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,int status_strings,int null_mbulk_as_null,zval * z_tab,void * ctx)3438 variant_reply_generic(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3439                       int status_strings, int null_mbulk_as_null,
3440                       zval *z_tab, void *ctx)
3441 {
3442     // Reply type, and reply size vars
3443     REDIS_REPLY_TYPE reply_type;
3444     long reply_info;
3445     zval z_ret;
3446 
3447     // Attempt to read our header
3448     if(redis_read_reply_type(redis_sock,&reply_type,&reply_info) < 0) {
3449         return -1;
3450     }
3451 
3452     /* Switch based on our top level reply type */
3453     switch(reply_type) {
3454         case TYPE_ERR:
3455         case TYPE_LINE:
3456             redis_read_variant_line(redis_sock, reply_type, status_strings, &z_ret);
3457             break;
3458         case TYPE_INT:
3459             ZVAL_LONG(&z_ret, reply_info);
3460             break;
3461         case TYPE_BULK:
3462             redis_read_variant_bulk(redis_sock, reply_info, &z_ret);
3463             break;
3464         case TYPE_MULTIBULK:
3465             if (reply_info > -1) {
3466                 array_init(&z_ret);
3467                 redis_read_multibulk_recursive(redis_sock, reply_info, status_strings, &z_ret);
3468             } else {
3469                 if (null_mbulk_as_null) {
3470                     ZVAL_NULL(&z_ret);
3471                 } else {
3472                     array_init(&z_ret);
3473                 }
3474             }
3475             break;
3476         default:
3477             zend_throw_exception_ex(redis_exception_ce, 0,
3478                 "protocol error, got '%c' as reply-type byte\n", reply_type);
3479             return FAILURE;
3480     }
3481 
3482     if (IS_ATOMIC(redis_sock)) {
3483         /* Set our return value */
3484         RETVAL_ZVAL(&z_ret, 0, 1);
3485     } else {
3486         add_next_index_zval(z_tab, &z_ret);
3487     }
3488 
3489     /* Success */
3490     return 0;
3491 }
3492 
3493 PHP_REDIS_API int
redis_read_raw_variant_reply(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)3494 redis_read_raw_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3495                              zval *z_tab, void *ctx)
3496 {
3497     return variant_reply_generic(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
3498                                  redis_sock->reply_literal,
3499                                  redis_sock->null_mbulk_as_null,
3500                                  z_tab, ctx);
3501 }
3502 
3503 PHP_REDIS_API int
redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)3504 redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3505                          zval *z_tab, void *ctx)
3506 {
3507     return variant_reply_generic(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, 0,
3508                                  redis_sock->null_mbulk_as_null, z_tab, ctx);
3509 }
3510 
3511 PHP_REDIS_API int
redis_read_variant_reply_strings(INTERNAL_FUNCTION_PARAMETERS,RedisSock * redis_sock,zval * z_tab,void * ctx)3512 redis_read_variant_reply_strings(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
3513                                  zval *z_tab, void *ctx)
3514 {
3515     return variant_reply_generic(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, 1, 0, z_tab, ctx);
3516 }
3517 
3518 PHP_REDIS_API
redis_extract_auth_info(zval * ztest,zend_string ** user,zend_string ** pass)3519 int redis_extract_auth_info(zval *ztest, zend_string **user, zend_string **pass) {
3520     zval *zv;
3521     HashTable *ht;
3522     int num;
3523 
3524     /* The user may wish to send us something like [NULL, 'password'] or
3525      * [false, 'password'] so don't convert NULL or FALSE into "". */
3526     #define TRY_SET_AUTH_ARG(zv, ppzstr) \
3527         do { \
3528             if (Z_TYPE_P(zv) != IS_NULL && Z_TYPE_P(zv) != IS_FALSE) { \
3529                 *(ppzstr) = zval_get_string(zv); \
3530             } \
3531         } while (0)
3532 
3533     /* Null out user and password */
3534     *user = *pass = NULL;
3535 
3536     /* User passed nothing */
3537     if (ztest == NULL)
3538         return FAILURE;
3539 
3540     /* Handle a non-array first */
3541     if (Z_TYPE_P(ztest) != IS_ARRAY) {
3542         *pass = zval_get_string(ztest);
3543         return SUCCESS;
3544     }
3545 
3546     /* Handle the array case */
3547     ht = Z_ARRVAL_P(ztest);
3548     num = zend_hash_num_elements(ht);
3549 
3550     /* Something other than one or two entries makes no sense */
3551     if (num != 1 && num != 2) {
3552         php_error_docref(NULL, E_WARNING, "When passing an array as auth it must have one or two elements!");
3553         return FAILURE;
3554     }
3555 
3556     if (num == 2) {
3557         if ((zv = REDIS_HASH_STR_FIND_STATIC(ht, "user")) ||
3558             (zv = zend_hash_index_find(ht, 0)))
3559         {
3560             TRY_SET_AUTH_ARG(zv, user);
3561         }
3562 
3563         if ((zv = REDIS_HASH_STR_FIND_STATIC(ht, "pass")) ||
3564             (zv = zend_hash_index_find(ht, 1)))
3565         {
3566             TRY_SET_AUTH_ARG(zv, pass);
3567         }
3568     } else if ((zv = REDIS_HASH_STR_FIND_STATIC(ht, "pass")) ||
3569                (zv = zend_hash_index_find(ht, 0)))
3570     {
3571         TRY_SET_AUTH_ARG(zv, pass);
3572     }
3573 
3574     /* If we at least have a password, we're good */
3575     if (*pass != NULL)
3576         return SUCCESS;
3577 
3578     /* Failure, clean everything up so caller doesn't need to care */
3579     if (*user) zend_string_release(*user);
3580     *user = NULL;
3581 
3582     return FAILURE;
3583 }
3584 
3585 /* Helper methods to extract configuration settings from a hash table */
3586 
redis_hash_str_find_type(HashTable * ht,const char * key,int keylen,int type)3587 zval *redis_hash_str_find_type(HashTable *ht, const char *key, int keylen, int type) {
3588     zval *zv = zend_hash_str_find(ht, key, keylen);
3589     if (zv == NULL || Z_TYPE_P(zv) != type)
3590         return NULL;
3591 
3592     return zv;
3593 }
3594 
redis_conf_double(HashTable * ht,const char * key,int keylen,double * dval)3595 void redis_conf_double(HashTable *ht, const char *key, int keylen, double *dval) {
3596     zval *zv = zend_hash_str_find(ht, key, keylen);
3597     if (zv == NULL)
3598         return;
3599 
3600     *dval = zval_get_double(zv);
3601 }
3602 
redis_conf_bool(HashTable * ht,const char * key,int keylen,int * ival)3603 void redis_conf_bool(HashTable *ht, const char *key, int keylen, int *ival) {
3604     zend_string *zstr = NULL;
3605 
3606     redis_conf_string(ht, key, keylen, &zstr);
3607     if (zstr == NULL)
3608         return;
3609 
3610     *ival = zend_string_equals_literal_ci(zstr, "true") ||
3611             zend_string_equals_literal_ci(zstr, "yes") ||
3612             zend_string_equals_literal_ci(zstr, "1");
3613 
3614     zend_string_release(zstr);
3615 }
3616 
redis_conf_zend_bool(HashTable * ht,const char * key,int keylen,zend_bool * bval)3617 void redis_conf_zend_bool(HashTable *ht, const char *key, int keylen, zend_bool *bval) {
3618     zval *zv = zend_hash_str_find(ht, key, keylen);
3619     if (zv == NULL)
3620         return;
3621 
3622     *bval = zend_is_true(zv);
3623 }
3624 
redis_conf_long(HashTable * ht,const char * key,int keylen,zend_long * lval)3625 void redis_conf_long(HashTable *ht, const char *key, int keylen, zend_long *lval) {
3626     zval *zv = zend_hash_str_find(ht, key, keylen);
3627     if (zv == NULL)
3628         return;
3629 
3630     *lval = zval_get_long(zv);
3631 }
3632 
redis_conf_int(HashTable * ht,const char * key,int keylen,int * ival)3633 void redis_conf_int(HashTable *ht, const char *key, int keylen, int *ival) {
3634     zval *zv = zend_hash_str_find(ht, key, keylen);
3635     if (zv == NULL)
3636         return;
3637 
3638     *ival = zval_get_long(zv);
3639 }
3640 
redis_conf_string(HashTable * ht,const char * key,size_t keylen,zend_string ** sval)3641 void redis_conf_string(HashTable *ht, const char *key, size_t keylen,
3642                        zend_string **sval)
3643 {
3644     zval *zv = zend_hash_str_find(ht, key, keylen);
3645     if (zv == NULL)
3646         return;
3647 
3648     *sval = zval_get_string(zv);
3649 }
3650 
redis_conf_zval(HashTable * ht,const char * key,size_t keylen,zval * zret,int copy,int dtor)3651 void redis_conf_zval(HashTable *ht, const char *key, size_t keylen, zval *zret,
3652                      int copy, int dtor)
3653 {
3654     zval *zv = zend_hash_str_find(ht, key, keylen);
3655     if (zv == NULL)
3656         return;
3657 
3658     ZVAL_ZVAL(zret, zv, copy, dtor);
3659 }
3660 
redis_conf_auth(HashTable * ht,const char * key,size_t keylen,zend_string ** user,zend_string ** pass)3661 void redis_conf_auth(HashTable *ht, const char *key, size_t keylen,
3662                      zend_string **user, zend_string **pass)
3663 {
3664     zval *zv = zend_hash_str_find(ht, key, keylen);
3665     if (zv == NULL)
3666         return;
3667 
3668     redis_extract_auth_info(zv, user, pass);
3669 }
3670 
3671 /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */
3672