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