xref: /php-src/ext/mysqlnd/mysqlnd_ps_codec.c (revision a1ab8462)
1 /*
2   +----------------------------------------------------------------------+
3   | Copyright (c) The PHP Group                                          |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 3.01 of the PHP license,      |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | https://www.php.net/license/3_01.txt                                 |
9   | If you did not receive a copy of the PHP license and are unable to   |
10   | obtain it through the world-wide-web, please send a note to          |
11   | license@php.net so we can mail you a copy immediately.               |
12   +----------------------------------------------------------------------+
13   | Authors: Andrey Hristov <andrey@php.net>                             |
14   |          Ulf Wendel <uw@php.net>                                     |
15   +----------------------------------------------------------------------+
16 */
17 
18 #include <math.h>
19 #include "php.h"
20 #include "mysqlnd.h"
21 #include "mysqlnd_wireprotocol.h"
22 #include "mysqlnd_connection.h"
23 #include "mysqlnd_ps.h"
24 #include "mysqlnd_priv.h"
25 #include "mysqlnd_debug.h"
26 #include "mysql_float_to_double.h"
27 
28 
29 enum mysqlnd_timestamp_type
30 {
31   MYSQLND_TIMESTAMP_NONE= -2,
32   MYSQLND_TIMESTAMP_ERROR= -1,
33   MYSQLND_TIMESTAMP_DATE= 0,
34   MYSQLND_TIMESTAMP_DATETIME= 1,
35   MYSQLND_TIMESTAMP_TIME= 2
36 };
37 
38 
39 struct st_mysqlnd_time
40 {
41   unsigned int  year, month, day, hour, minute, second;
42   zend_ulong second_part;
43   bool     neg;
44   enum mysqlnd_timestamp_type time_type;
45 };
46 
47 
48 struct st_mysqlnd_perm_bind mysqlnd_ps_fetch_functions[MYSQL_TYPE_LAST + 1];
49 
50 #define MYSQLND_PS_SKIP_RESULT_W_LEN	-1
51 #define MYSQLND_PS_SKIP_RESULT_STR		-2
52 
53 /* {{{ ps_fetch_from_1_to_8_bytes */
54 void
ps_fetch_from_1_to_8_bytes(zval * zv,const MYSQLND_FIELD * const field,const unsigned int pack_len,const zend_uchar ** row,unsigned int byte_count)55 ps_fetch_from_1_to_8_bytes(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len,
56 						   const zend_uchar ** row, unsigned int byte_count)
57 {
58 	bool is_bit = field->type == MYSQL_TYPE_BIT;
59 	DBG_ENTER("ps_fetch_from_1_to_8_bytes");
60 	DBG_INF_FMT("zv=%p byte_count=%u", zv, byte_count);
61 	if (field->flags & UNSIGNED_FLAG) {
62 		uint64_t uval = 0;
63 
64 		switch (byte_count) {
65 			case 8:uval = is_bit? (uint64_t) bit_uint8korr(*row):(uint64_t) uint8korr(*row);break;
66 			case 7:uval = bit_uint7korr(*row);break;
67 			case 6:uval = bit_uint6korr(*row);break;
68 			case 5:uval = bit_uint5korr(*row);break;
69 			case 4:uval = is_bit? (uint64_t) bit_uint4korr(*row):(uint64_t) uint4korr(*row);break;
70 			case 3:uval = is_bit? (uint64_t) bit_uint3korr(*row):(uint64_t) uint3korr(*row);break;
71 			case 2:uval = is_bit? (uint64_t) bit_uint2korr(*row):(uint64_t) uint2korr(*row);break;
72 			case 1:uval = (uint64_t) uint1korr(*row);break;
73 		}
74 
75 		if (field->flags & ZEROFILL_FLAG) {
76 			DBG_INF("stringify due to zerofill");
77 			ZVAL_STR(zv, zend_strpprintf(0, "%0*" PRIu64, (int) field->length, uval));
78 		} else
79 #if SIZEOF_ZEND_LONG==4
80 		if (uval > INT_MAX) {
81 			DBG_INF("stringify");
82 			ZVAL_STR(zv, zend_u64_to_str(uval));
83 		} else
84 #endif /* #if SIZEOF_LONG==4 */
85 		{
86 			if (byte_count < 8 || uval <= L64(9223372036854775807)) {
87 				ZVAL_LONG(zv, (zend_long) uval); /* the cast is safe, we are in the range */
88 			} else {
89 				DBG_INF("stringify");
90 				ZVAL_STR(zv, zend_u64_to_str(uval));
91 			}
92 		}
93 	} else {
94 		/* SIGNED */
95 		int64_t lval = 0;
96 		switch (byte_count) {
97 			case 8:lval = (int64_t) sint8korr(*row);break;
98 			/*
99 			  7, 6 and 5 are not possible.
100 			  BIT is only unsigned, thus only uint5|6|7 macros exist
101 			*/
102 			case 4:lval = (int64_t) sint4korr(*row);break;
103 			case 3:lval = (int64_t) sint3korr(*row);break;
104 			case 2:lval = (int64_t) sint2korr(*row);break;
105 			case 1:lval = (int64_t) *(int8_t*)*row;break;
106 		}
107 
108 #if SIZEOF_ZEND_LONG==4
109 		if ((L64(2147483647) < (int64_t) lval) || (L64(-2147483648) > (int64_t) lval)) {
110 			DBG_INF("stringify");
111 			ZVAL_STR(zv, zend_i64_to_str(lval));
112 		} else
113 #endif /* SIZEOF */
114 		{
115 			ZVAL_LONG(zv, (zend_long) lval); /* the cast is safe, we are in the range */
116 		}
117 	}
118 
119 	(*row)+= byte_count;
120 	DBG_VOID_RETURN;
121 }
122 /* }}} */
123 
124 
125 /* {{{ ps_fetch_null */
126 static void
ps_fetch_null(zval * zv,const MYSQLND_FIELD * const field,const unsigned int pack_len,const zend_uchar ** row)127 ps_fetch_null(zval *zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row)
128 {
129 	ZVAL_NULL(zv);
130 }
131 /* }}} */
132 
133 
134 /* {{{ ps_fetch_int8 */
135 static void
ps_fetch_int8(zval * zv,const MYSQLND_FIELD * const field,const unsigned int pack_len,const zend_uchar ** row)136 ps_fetch_int8(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row)
137 {
138 	ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, 1);
139 }
140 /* }}} */
141 
142 
143 /* {{{ ps_fetch_int16 */
144 static void
ps_fetch_int16(zval * zv,const MYSQLND_FIELD * const field,const unsigned int pack_len,const zend_uchar ** row)145 ps_fetch_int16(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row)
146 {
147 	ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, 2);
148 }
149 /* }}} */
150 
151 
152 /* {{{ ps_fetch_int32 */
153 static void
ps_fetch_int32(zval * zv,const MYSQLND_FIELD * const field,const unsigned int pack_len,const zend_uchar ** row)154 ps_fetch_int32(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row)
155 {
156 	ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, 4);
157 }
158 /* }}} */
159 
160 
161 /* {{{ ps_fetch_int64 */
162 static void
ps_fetch_int64(zval * zv,const MYSQLND_FIELD * const field,const unsigned int pack_len,const zend_uchar ** row)163 ps_fetch_int64(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row)
164 {
165 	ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, 8);
166 }
167 /* }}} */
168 
169 
170 /* {{{ ps_fetch_float */
171 static void
ps_fetch_float(zval * zv,const MYSQLND_FIELD * const field,const unsigned int pack_len,const zend_uchar ** row)172 ps_fetch_float(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row)
173 {
174 	float fval;
175 	double dval;
176 	DBG_ENTER("ps_fetch_float");
177 	float4get(fval, *row);
178 	(*row)+= 4;
179 	DBG_INF_FMT("value=%f", fval);
180 
181 #ifndef NOT_FIXED_DEC
182 # define NOT_FIXED_DEC 31
183 #endif
184 
185 	dval = mysql_float_to_double(fval, (field->decimals >= NOT_FIXED_DEC) ? -1 : (int)field->decimals);
186 
187 	ZVAL_DOUBLE(zv, dval);
188 	DBG_VOID_RETURN;
189 }
190 /* }}} */
191 
192 
193 /* {{{ ps_fetch_double */
194 static void
ps_fetch_double(zval * zv,const MYSQLND_FIELD * const field,const unsigned int pack_len,const zend_uchar ** row)195 ps_fetch_double(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row)
196 {
197 	double value;
198 	DBG_ENTER("ps_fetch_double");
199 	float8get(value, *row);
200 	ZVAL_DOUBLE(zv, value);
201 	(*row)+= 8;
202 	DBG_INF_FMT("value=%f", value);
203 	DBG_VOID_RETURN;
204 }
205 /* }}} */
206 
207 
208 /* {{{ ps_fetch_time */
209 static void
ps_fetch_time(zval * zv,const MYSQLND_FIELD * const field,const unsigned int pack_len,const zend_uchar ** row)210 ps_fetch_time(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row)
211 {
212 	struct st_mysqlnd_time t;
213 	zend_ulong length; /* First byte encodes the length */
214 	DBG_ENTER("ps_fetch_time");
215 
216 	if ((length = php_mysqlnd_net_field_length(row))) {
217 		const zend_uchar * to = *row;
218 
219 		t.time_type = MYSQLND_TIMESTAMP_TIME;
220 		t.neg			= (bool) to[0];
221 
222 		t.day			= (zend_ulong) sint4korr(to+1);
223 		t.hour			= (unsigned int) to[5];
224 		t.minute		= (unsigned int) to[6];
225 		t.second		= (unsigned int) to[7];
226 		t.second_part	= (length > 8) ? (zend_ulong) sint4korr(to+8) : 0;
227 		t.year			= t.month= 0;
228 		if (t.day) {
229 			/* Convert days to hours at once */
230 			t.hour += t.day*24;
231 			t.day	= 0;
232 		}
233 
234 		(*row) += length;
235 	} else {
236 		memset(&t, 0, sizeof(t));
237 		t.time_type = MYSQLND_TIMESTAMP_TIME;
238 	}
239 
240 	if (field->decimals > 0 && field->decimals < 7) {
241 		ZVAL_STR(zv, zend_strpprintf(0, "%s%02u:%02u:%02u.%0*u",
242 			(t.neg ? "-" : ""), t.hour, t.minute, t.second, field->decimals,
243 			(uint32_t) (t.second_part / pow(10, 6 - field->decimals))));
244 	} else {
245 		 ZVAL_STR(zv, zend_strpprintf(0, "%s%02u:%02u:%02u",
246 			(t.neg ? "-" : ""), t.hour, t.minute, t.second));
247 	}
248 	DBG_VOID_RETURN;
249 }
250 /* }}} */
251 
252 
253 /* {{{ ps_fetch_date */
254 static void
ps_fetch_date(zval * zv,const MYSQLND_FIELD * const field,const unsigned int pack_len,const zend_uchar ** row)255 ps_fetch_date(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row)
256 {
257 	struct st_mysqlnd_time t = {0};
258 	zend_ulong length; /* First byte encodes the length*/
259 	DBG_ENTER("ps_fetch_date");
260 
261 	if ((length = php_mysqlnd_net_field_length(row))) {
262 		const zend_uchar * to = *row;
263 
264 		t.time_type = MYSQLND_TIMESTAMP_DATE;
265 		t.neg = 0;
266 
267 		t.second_part = t.hour = t.minute = t.second = 0;
268 
269 		t.year	= (unsigned int) sint2korr(to);
270 		t.month = (unsigned int) to[2];
271 		t.day	= (unsigned int) to[3];
272 
273 		(*row)+= length;
274 	} else {
275 		memset(&t, 0, sizeof(t));
276 		t.time_type = MYSQLND_TIMESTAMP_DATE;
277 	}
278 
279 	ZVAL_STR(zv, zend_strpprintf(0, "%04u-%02u-%02u", t.year, t.month, t.day));
280 	DBG_VOID_RETURN;
281 }
282 /* }}} */
283 
284 
285 /* {{{ ps_fetch_datetime */
286 static void
ps_fetch_datetime(zval * zv,const MYSQLND_FIELD * const field,const unsigned int pack_len,const zend_uchar ** row)287 ps_fetch_datetime(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row)
288 {
289 	struct st_mysqlnd_time t;
290 	zend_ulong length; /* First byte encodes the length*/
291 	DBG_ENTER("ps_fetch_datetime");
292 
293 	if ((length = php_mysqlnd_net_field_length(row))) {
294 		const zend_uchar * to = *row;
295 
296 		t.time_type = MYSQLND_TIMESTAMP_DATETIME;
297 		t.neg = 0;
298 
299 		t.year	 = (unsigned int) sint2korr(to);
300 		t.month = (unsigned int) to[2];
301 		t.day	 = (unsigned int) to[3];
302 
303 		if (length > 4) {
304 			t.hour	 = (unsigned int) to[4];
305 			t.minute = (unsigned int) to[5];
306 			t.second = (unsigned int) to[6];
307 		} else {
308 			t.hour = t.minute = t.second= 0;
309 		}
310 		t.second_part = (length > 7) ? (zend_ulong) sint4korr(to+7) : 0;
311 
312 		(*row)+= length;
313 	} else {
314 		memset(&t, 0, sizeof(t));
315 		t.time_type = MYSQLND_TIMESTAMP_DATETIME;
316 	}
317 
318 	if (field->decimals > 0 && field->decimals < 7) {
319 		ZVAL_STR(zv, zend_strpprintf(0, "%04u-%02u-%02u %02u:%02u:%02u.%0*u",
320 			t.year, t.month, t.day, t.hour, t.minute, t.second, field->decimals,
321 			(uint32_t) (t.second_part / pow(10, 6 - field->decimals))));
322 	} else {
323 		ZVAL_STR(zv,  zend_strpprintf(0, "%04u-%02u-%02u %02u:%02u:%02u",
324 			t.year, t.month, t.day, t.hour, t.minute, t.second));
325 	}
326 	DBG_VOID_RETURN;
327 }
328 /* }}} */
329 
330 
331 /* {{{ ps_fetch_string */
332 static void
ps_fetch_string(zval * zv,const MYSQLND_FIELD * const field,const unsigned int pack_len,const zend_uchar ** row)333 ps_fetch_string(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row)
334 {
335 	const zend_ulong length = php_mysqlnd_net_field_length(row);
336 	DBG_ENTER("ps_fetch_string");
337 	DBG_INF_FMT("len = " ZEND_ULONG_FMT, length);
338 	DBG_INF("copying from the row buffer");
339 	ZVAL_STRINGL_FAST(zv, (char *)*row, length);
340 
341 	(*row) += length;
342 	DBG_VOID_RETURN;
343 }
344 /* }}} */
345 
346 
347 /* {{{ ps_fetch_bit */
348 static void
ps_fetch_bit(zval * zv,const MYSQLND_FIELD * const field,const unsigned int pack_len,const zend_uchar ** row)349 ps_fetch_bit(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row)
350 {
351 	const zend_ulong length = php_mysqlnd_net_field_length(row);
352 	ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, length);
353 }
354 /* }}} */
355 
356 
357 /* {{{ _mysqlnd_init_ps_fetch_subsystem */
_mysqlnd_init_ps_fetch_subsystem(void)358 void _mysqlnd_init_ps_fetch_subsystem(void)
359 {
360 	memset(mysqlnd_ps_fetch_functions, 0, sizeof(mysqlnd_ps_fetch_functions));
361 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_NULL].func		= ps_fetch_null;
362 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_NULL].pack_len	= 0;
363 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_NULL].php_type	= IS_NULL;
364 
365 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY].func		= ps_fetch_int8;
366 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY].pack_len	= 1;
367 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY].php_type	= IS_LONG;
368 
369 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_SHORT].func		= ps_fetch_int16;
370 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_SHORT].pack_len	= 2;
371 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_SHORT].php_type	= IS_LONG;
372 
373 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_YEAR].func		= ps_fetch_int16;
374 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_YEAR].pack_len	= 2;
375 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_YEAR].php_type	= IS_LONG;
376 
377 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_INT24].func		= ps_fetch_int32;
378 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_INT24].pack_len	= 4;
379 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_INT24].php_type	= IS_LONG;
380 
381 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG].func		= ps_fetch_int32;
382 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG].pack_len	= 4;
383 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG].php_type	= IS_LONG;
384 
385 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONGLONG].func	= ps_fetch_int64;
386 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONGLONG].pack_len= 8;
387 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONGLONG].php_type= IS_LONG;
388 
389 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_FLOAT].func		= ps_fetch_float;
390 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_FLOAT].pack_len	= 4;
391 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_FLOAT].php_type	= IS_DOUBLE;
392 
393 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_DOUBLE].func		= ps_fetch_double;
394 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_DOUBLE].pack_len	= 8;
395 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_DOUBLE].php_type	= IS_DOUBLE;
396 
397 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIME].func		= ps_fetch_time;
398 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIME].pack_len	= MYSQLND_PS_SKIP_RESULT_W_LEN;
399 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIME].php_type	= IS_STRING;
400 
401 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATE].func		= ps_fetch_date;
402 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATE].pack_len	= MYSQLND_PS_SKIP_RESULT_W_LEN;
403 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATE].php_type	= IS_STRING;
404 
405 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDATE].func		= ps_fetch_string;
406 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDATE].pack_len	= MYSQLND_PS_SKIP_RESULT_W_LEN;
407 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDATE].php_type	= IS_STRING;
408 
409 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATETIME].func	= ps_fetch_datetime;
410 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATETIME].pack_len= MYSQLND_PS_SKIP_RESULT_W_LEN;
411 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATETIME].php_type= IS_STRING;
412 
413 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIMESTAMP].func	= ps_fetch_datetime;
414 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIMESTAMP].pack_len= MYSQLND_PS_SKIP_RESULT_W_LEN;
415 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIMESTAMP].php_type= IS_STRING;
416 
417 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_VECTOR].func		= ps_fetch_string;
418 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_VECTOR].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
419 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_VECTOR].php_type	= IS_STRING;
420 
421 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_JSON].func	= ps_fetch_string;
422 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_JSON].pack_len= MYSQLND_PS_SKIP_RESULT_STR;
423 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_JSON].php_type = IS_STRING;
424 
425 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].func	= ps_fetch_string;
426 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].pack_len= MYSQLND_PS_SKIP_RESULT_STR;
427 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].php_type = IS_STRING;
428 
429 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].func		= ps_fetch_string;
430 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
431 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].php_type	= IS_STRING;
432 
433 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].func		= ps_fetch_string;
434 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
435 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].php_type	= IS_STRING;
436 
437 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].func		= ps_fetch_string;
438 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
439 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].php_type	= IS_STRING;
440 
441 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_BIT].func		= ps_fetch_bit;
442 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_BIT].pack_len	= 8;
443 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_BIT].php_type	= IS_LONG;
444 
445 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].func		= ps_fetch_string;
446 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
447 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].php_type = IS_STRING;
448 
449 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_VARCHAR].func		= ps_fetch_string;
450 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_VARCHAR].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
451 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_VARCHAR].php_type	= IS_STRING;
452 
453 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_STRING].func			= ps_fetch_string;
454 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_STRING].pack_len		= MYSQLND_PS_SKIP_RESULT_STR;
455 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_STRING].php_type	= IS_STRING;
456 
457 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_DECIMAL].func		= ps_fetch_string;
458 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_DECIMAL].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
459 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_DECIMAL].php_type	= IS_STRING;
460 
461 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].func		= ps_fetch_string;
462 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
463 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].php_type	= IS_STRING;
464 
465 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_ENUM].func		= ps_fetch_string;
466 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_ENUM].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
467 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_ENUM].php_type	= IS_STRING;
468 
469 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_SET].func			= ps_fetch_string;
470 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_SET].pack_len		= MYSQLND_PS_SKIP_RESULT_STR;
471 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_SET].php_type		= IS_STRING;
472 
473 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_GEOMETRY].func	= ps_fetch_string;
474 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_GEOMETRY].pack_len= MYSQLND_PS_SKIP_RESULT_STR;
475 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_GEOMETRY].php_type= IS_STRING;
476 }
477 /* }}} */
478 
479 
480 /* {{{ mysqlnd_stmt_copy_it */
481 static enum_func_status
mysqlnd_stmt_copy_it(zval ** copies,zval * original,unsigned int param_count,unsigned int current)482 mysqlnd_stmt_copy_it(zval ** copies, zval * original, unsigned int param_count, unsigned int current)
483 {
484 	if (!*copies) {
485 		*copies = mnd_ecalloc(param_count, sizeof(zval));
486 	}
487 	if (*copies) {
488 		ZVAL_COPY(&(*copies)[current], original);
489 		return PASS;
490 	}
491 	return FAIL;
492 }
493 /* }}} */
494 
495 
496 /* {{{ mysqlnd_stmt_free_copies */
497 static void
mysqlnd_stmt_free_copies(MYSQLND_STMT_DATA * stmt,zval * copies)498 mysqlnd_stmt_free_copies(MYSQLND_STMT_DATA * stmt, zval *copies)
499 {
500 	if (copies) {
501 		unsigned int i;
502 		for (i = 0; i < stmt->param_count; i++) {
503 			zval_ptr_dtor(&copies[i]);
504 		}
505 		mnd_efree(copies);
506 	}
507 }
508 /* }}} */
509 
510 
511 /* {{{ mysqlnd_stmt_execute_check_n_enlarge_buffer */
512 static enum_func_status
mysqlnd_stmt_execute_check_n_enlarge_buffer(zend_uchar ** buf,zend_uchar ** p,size_t * buf_len,zend_uchar * const provided_buffer,size_t needed_bytes)513 mysqlnd_stmt_execute_check_n_enlarge_buffer(zend_uchar **buf, zend_uchar **p, size_t * buf_len, zend_uchar * const provided_buffer, size_t needed_bytes)
514 {
515 	const size_t overalloc = 5;
516 	size_t left = (*buf_len - (*p - *buf));
517 
518 	if (left < (needed_bytes + overalloc)) {
519 		const size_t offset = *p - *buf;
520 		zend_uchar *tmp_buf;
521 		*buf_len = offset + needed_bytes + overalloc;
522 		tmp_buf = mnd_emalloc(*buf_len);
523 		if (!tmp_buf) {
524 			return FAIL;
525 		}
526 		memcpy(tmp_buf, *buf, offset);
527 		if (*buf != provided_buffer) {
528 			mnd_efree(*buf);
529 		}
530 		*buf = tmp_buf;
531 		/* Update our pos pointer */
532 		*p = *buf + offset;
533 	}
534 	return PASS;
535 }
536 /* }}} */
537 
538 
539 /* {{{ mysqlnd_stmt_execute_prepare_param_types */
540 static enum_func_status
mysqlnd_stmt_execute_prepare_param_types(MYSQLND_STMT_DATA * stmt,zval ** copies_param,int * resend_types_next_time)541 mysqlnd_stmt_execute_prepare_param_types(MYSQLND_STMT_DATA * stmt, zval ** copies_param, int * resend_types_next_time)
542 {
543 	unsigned int i;
544 	DBG_ENTER("mysqlnd_stmt_execute_prepare_param_types");
545 	for (i = 0; i < stmt->param_count; i++) {
546 		const short current_type = stmt->param_bind[i].type;
547 		zval *parameter = &stmt->param_bind[i].zv;
548 
549 		ZVAL_DEREF(parameter);
550 		if (!Z_ISNULL_P(parameter) && (current_type == MYSQL_TYPE_LONG || current_type == MYSQL_TYPE_LONGLONG || current_type == MYSQL_TYPE_TINY)) {
551 			/* always copy the var, because we do many conversions */
552 			if (Z_TYPE_P(parameter) != IS_LONG &&
553 				PASS != mysqlnd_stmt_copy_it(copies_param, parameter, stmt->param_count, i))
554 			{
555 				SET_OOM_ERROR(stmt->error_info);
556 				goto end;
557 			}
558 			/*
559 			  if it doesn't fit in a long send it as a string.
560 			  Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX
561 			*/
562 			if (Z_TYPE_P(parameter) != IS_LONG) {
563 				zval *tmp_data = (*copies_param && !Z_ISUNDEF((*copies_param)[i]))? &(*copies_param)[i]: parameter;
564 				/*
565 				  Because converting to double and back to long can lead
566 				  to losing precision we need second variable. Conversion to double is to see if
567 				  value is too big for a long. As said, precision could be lost.
568 				*/
569 				double d = zval_get_double(tmp_data);
570 
571 				/*
572 				  if it doesn't fit in a long send it as a string.
573 				  Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX
574 				  We do transformation here, which will be used later when sending types. The code later relies on this.
575 				*/
576 				if (d >= (double) ZEND_LONG_MAX || d < (double) ZEND_LONG_MIN) {
577 					stmt->send_types_to_server = *resend_types_next_time = 1;
578 					convert_to_string(tmp_data);
579 				} else {
580 					convert_to_long(tmp_data);
581 				}
582 			}
583 		}
584 	}
585 	DBG_RETURN(PASS);
586 end:
587 	DBG_RETURN(FAIL);
588 }
589 /* }}} */
590 
591 
592 /* {{{ mysqlnd_stmt_execute_store_types */
593 static void
mysqlnd_stmt_execute_store_types(MYSQLND_STMT_DATA * stmt,zval * copies,zend_uchar ** p)594 mysqlnd_stmt_execute_store_types(MYSQLND_STMT_DATA * stmt, zval * copies, zend_uchar ** p)
595 {
596 	unsigned int i;
597 	for (i = 0; i < stmt->param_count; i++) {
598 		short current_type = stmt->param_bind[i].type;
599 		zval *parameter = &stmt->param_bind[i].zv;
600 		/* our types are not unsigned */
601 #if SIZEOF_ZEND_LONG==8
602 		if (current_type == MYSQL_TYPE_LONG) {
603 			current_type = MYSQL_TYPE_LONGLONG;
604 		}
605 #endif
606 		ZVAL_DEREF(parameter);
607 		if (!Z_ISNULL_P(parameter) && (current_type == MYSQL_TYPE_LONG || current_type == MYSQL_TYPE_LONGLONG)) {
608 			/*
609 			  if it doesn't fit in a long send it as a string.
610 			  Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX
611 			*/
612 			if (Z_TYPE_P(parameter) != IS_LONG) {
613 				const zval *tmp_data = (copies && !Z_ISUNDEF(copies[i]))? &copies[i] : parameter;
614 				/*
615 				  In case of IS_LONG we do nothing, it is ok, in case of string, we just need to set current_type.
616 				  The actual transformation has been performed several dozens line above.
617 				*/
618 				if (Z_TYPE_P(tmp_data) == IS_STRING) {
619 					current_type = MYSQL_TYPE_VAR_STRING;
620 					/*
621 					  don't change stmt->param_bind[i].type to MYSQL_TYPE_VAR_STRING
622 					  we force convert_to_long in all cases, thus the type will be right in the next switch.
623 					  if the type is however not long, then we will do a goto in the next switch.
624 					  We want to preserve the original bind type given by the user. Thus, we do these hacks.
625 					*/
626 				}
627 			}
628 		}
629 		int2store(*p, current_type);
630 		*p+= 2;
631 	}
632 }
633 /* }}} */
634 
635 
636 /* {{{ mysqlnd_stmt_execute_calculate_param_values_size */
637 static enum_func_status
mysqlnd_stmt_execute_calculate_param_values_size(MYSQLND_STMT_DATA * stmt,zval ** copies_param,size_t * data_size)638 mysqlnd_stmt_execute_calculate_param_values_size(MYSQLND_STMT_DATA * stmt, zval ** copies_param, size_t * data_size)
639 {
640 	unsigned int i;
641 	DBG_ENTER("mysqlnd_stmt_execute_calculate_param_values_size");
642 	for (i = 0; i < stmt->param_count; i++) {
643 		unsigned short is_longlong = 0;
644 		unsigned int j;
645 		zval *bind_var, *the_var = &stmt->param_bind[i].zv;
646 
647 		bind_var = the_var;
648 		ZVAL_DEREF(the_var);
649 		if ((stmt->param_bind[i].type != MYSQL_TYPE_LONG_BLOB && Z_TYPE_P(the_var) == IS_NULL)) {
650 			continue;
651 		}
652 
653 		if (Z_ISREF_P(bind_var)) {
654 			for (j = i + 1; j < stmt->param_count; j++) {
655 				if (Z_ISREF(stmt->param_bind[j].zv) && Z_REFVAL(stmt->param_bind[j].zv) == the_var) {
656 					/* Double binding of the same zval, make a copy */
657 					if (!*copies_param || Z_ISUNDEF((*copies_param)[i])) {
658 						if (PASS != mysqlnd_stmt_copy_it(copies_param, the_var, stmt->param_count, i)) {
659 							SET_OOM_ERROR(stmt->error_info);
660 							goto end;
661 						}
662 					}
663 					break;
664 				}
665 			}
666 		}
667 
668 		switch (stmt->param_bind[i].type) {
669 			case MYSQL_TYPE_DOUBLE:
670 				*data_size += 8;
671 				if (Z_TYPE_P(the_var) != IS_DOUBLE) {
672 					if (!*copies_param || Z_ISUNDEF((*copies_param)[i])) {
673 						if (PASS != mysqlnd_stmt_copy_it(copies_param, the_var, stmt->param_count, i)) {
674 							SET_OOM_ERROR(stmt->error_info);
675 							goto end;
676 						}
677 					}
678 				}
679 				break;
680 			case MYSQL_TYPE_LONGLONG:
681 				is_longlong = 4;
682 				ZEND_FALLTHROUGH;
683 			case MYSQL_TYPE_LONG:
684 				{
685 					zval *tmp_data = (*copies_param && !Z_ISUNDEF((*copies_param)[i]))? &(*copies_param)[i]: the_var;
686 					if (Z_TYPE_P(tmp_data) == IS_STRING) {
687 						goto use_string;
688 					}
689 					convert_to_long(tmp_data);
690 				}
691 				*data_size += 4 + is_longlong;
692 				break;
693 			case MYSQL_TYPE_LONG_BLOB:
694 				if (!(stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED)) {
695 					/*
696 					  User hasn't sent anything, we will send empty string.
697 					  Empty string has length of 0, encoded in 1 byte. No real
698 					  data will follow after it.
699 					*/
700 					(*data_size)++;
701 				}
702 				break;
703 			case MYSQL_TYPE_VAR_STRING:
704 use_string:
705 				*data_size += 8; /* max 8 bytes for size */
706 				if (Z_TYPE_P(the_var) != IS_STRING) {
707 					if (!*copies_param || Z_ISUNDEF((*copies_param)[i])) {
708 						if (PASS != mysqlnd_stmt_copy_it(copies_param, the_var, stmt->param_count, i)) {
709 							SET_OOM_ERROR(stmt->error_info);
710 							goto end;
711 						}
712 					}
713 					the_var = &((*copies_param)[i]);
714 				}
715 
716 				if (!try_convert_to_string(the_var)) {
717 					goto end;
718 				}
719 				*data_size += Z_STRLEN_P(the_var);
720 				break;
721 		}
722 	}
723 	DBG_RETURN(PASS);
724 end:
725 	DBG_RETURN(FAIL);
726 }
727 /* }}} */
728 
729 
730 /* {{{ mysqlnd_stmt_execute_store_param_values */
731 static void
mysqlnd_stmt_execute_store_param_values(MYSQLND_STMT_DATA * stmt,zval * copies,zend_uchar * buf,zend_uchar ** p,size_t null_byte_offset)732 mysqlnd_stmt_execute_store_param_values(MYSQLND_STMT_DATA * stmt, zval * copies, zend_uchar * buf, zend_uchar ** p, size_t null_byte_offset)
733 {
734 	unsigned int i;
735 	for (i = 0; i < stmt->param_count; i++) {
736 		zval *data, *parameter = &stmt->param_bind[i].zv;
737 
738 		ZVAL_DEREF(parameter);
739 		data = (copies && !Z_ISUNDEF(copies[i]))? &copies[i]: parameter;
740 		/* Handle long data */
741 		if (!Z_ISUNDEF_P(parameter) && Z_TYPE_P(data) == IS_NULL) {
742 			(buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7));
743 		} else {
744 			switch (stmt->param_bind[i].type) {
745 				case MYSQL_TYPE_DOUBLE:
746 					convert_to_double(data);
747 					float8store(*p, Z_DVAL_P(data));
748 					(*p) += 8;
749 					break;
750 				case MYSQL_TYPE_LONGLONG:
751 					if (Z_TYPE_P(data) == IS_STRING) {
752 						goto send_string;
753 					}
754 					/* data has alreade been converted to long */
755 					int8store(*p, Z_LVAL_P(data));
756 					(*p) += 8;
757 					break;
758 				case MYSQL_TYPE_LONG:
759 					if (Z_TYPE_P(data) == IS_STRING) {
760 						goto send_string;
761 					}
762 					/* data has alreade been converted to long */
763 					int4store(*p, Z_LVAL_P(data));
764 					(*p) += 4;
765 					break;
766 				case MYSQL_TYPE_TINY:
767 					if (Z_TYPE_P(data) == IS_STRING) {
768 						goto send_string;
769 					}
770 					int1store(*p, Z_LVAL_P(data));
771 					(*p)++;
772 					break;
773 				case MYSQL_TYPE_LONG_BLOB:
774 					if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) {
775 						stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
776 					} else {
777 						/* send_long_data() not called, send empty string */
778 						*p = php_mysqlnd_net_store_length(*p, 0);
779 					}
780 					break;
781 				case MYSQL_TYPE_VAR_STRING:
782 send_string:
783 					{
784 						const size_t len = Z_STRLEN_P(data);
785 						/* to is after p. The latter hasn't been moved */
786 						*p = php_mysqlnd_net_store_length(*p, len);
787 						memcpy(*p, Z_STRVAL_P(data), len);
788 						(*p) += len;
789 					}
790 					break;
791 				default:
792 					/* Won't happen, but set to NULL */
793 					(buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7));
794 					break;
795 			}
796 		}
797 	}
798 }
799 /* }}} */
800 
801 
802 /* {{{ mysqlnd_stmt_execute_store_params */
803 static enum_func_status
mysqlnd_stmt_execute_store_params(MYSQLND_STMT * s,zend_uchar ** buf,zend_uchar ** p,size_t * buf_len)804 mysqlnd_stmt_execute_store_params(MYSQLND_STMT * s, zend_uchar **buf, zend_uchar **p, size_t *buf_len )
805 {
806 	MYSQLND_STMT_DATA * stmt = s->data;
807 	zend_uchar * provided_buffer = *buf;
808 	size_t data_size = 0;
809 	zval *copies = NULL;/* if there are different types */
810 	enum_func_status ret = FAIL;
811 	int resend_types_next_time = 0;
812 	size_t null_byte_offset;
813 
814 	DBG_ENTER("mysqlnd_stmt_execute_store_params");
815 
816 	{
817 		unsigned int null_count = (stmt->param_count + 7) / 8;
818 		if (FAIL == mysqlnd_stmt_execute_check_n_enlarge_buffer(buf, p, buf_len, provided_buffer, null_count)) {
819 			SET_OOM_ERROR(stmt->error_info);
820 			goto end;
821 		}
822 		/* put `null` bytes */
823 		null_byte_offset = *p - *buf;
824 		memset(*p, 0, null_count);
825 		*p += null_count;
826 	}
827 
828 /* 1. Store type information */
829 	/*
830 	  check if need to send the types even if stmt->send_types_to_server is 0. This is because
831 	  if we send "i" (42) then the type will be int and the server will expect int. However, if next
832 	  time we try to send > LONG_MAX, the conversion to string will send a string and the server
833 	  won't expect it and interpret the value as 0. Thus we need to resend the types, if any such values
834 	  occur, and force resend for the next execution.
835 	*/
836 	if (FAIL == mysqlnd_stmt_execute_prepare_param_types(stmt, &copies, &resend_types_next_time)) {
837 		goto end;
838 	}
839 
840 	int1store(*p, stmt->send_types_to_server);
841 	(*p)++;
842 
843 	if (stmt->send_types_to_server) {
844 		if (FAIL == mysqlnd_stmt_execute_check_n_enlarge_buffer(buf, p, buf_len, provided_buffer, stmt->param_count * 2)) {
845 			SET_OOM_ERROR(stmt->error_info);
846 			goto end;
847 		}
848 		mysqlnd_stmt_execute_store_types(stmt, copies, p);
849 	}
850 
851 	stmt->send_types_to_server = resend_types_next_time;
852 
853 /* 2. Store data */
854 	/* 2.1 Calculate how much space we need */
855 	if (FAIL == mysqlnd_stmt_execute_calculate_param_values_size(stmt, &copies, &data_size)) {
856 		goto end;
857 	}
858 
859 	/* 2.2 Enlarge the buffer, if needed */
860 	if (FAIL == mysqlnd_stmt_execute_check_n_enlarge_buffer(buf, p, buf_len, provided_buffer, data_size)) {
861 		SET_OOM_ERROR(stmt->error_info);
862 		goto end;
863 	}
864 
865 	/* 2.3 Store the actual data */
866 	mysqlnd_stmt_execute_store_param_values(stmt, copies, *buf, p, null_byte_offset);
867 
868 	ret = PASS;
869 end:
870 	mysqlnd_stmt_free_copies(stmt, copies);
871 
872 	DBG_INF_FMT("ret=%s", ret == PASS? "PASS":"FAIL");
873 	DBG_RETURN(ret);
874 }
875 /* }}} */
876 
877 
878 /* {{{ mysqlnd_stmt_execute_generate_request */
879 enum_func_status
mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s,zend_uchar ** request,size_t * request_len,bool * free_buffer)880 mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, bool * free_buffer)
881 {
882 	MYSQLND_STMT_DATA * stmt = s->data;
883 	zend_uchar	*p = stmt->execute_cmd_buffer.buffer,
884 				*cmd_buffer = stmt->execute_cmd_buffer.buffer;
885 	size_t cmd_buffer_length = stmt->execute_cmd_buffer.length;
886 	enum_func_status ret = PASS;
887 
888 	DBG_ENTER("mysqlnd_stmt_execute_generate_request");
889 
890 	int4store(p, stmt->stmt_id);
891 	p += 4;
892 
893 	/* flags is 4 bytes, we store just 1 */
894 	int1store(p, (zend_uchar) stmt->flags);
895 	p++;
896 
897 	/* Make it all zero */
898 	int4store(p, 0);
899 
900 	int1store(p, 1); /* and send 1 for iteration count */
901 	p+= 4;
902 
903 	if (stmt->param_count != 0) {
904 	    ret = mysqlnd_stmt_execute_store_params(s, &cmd_buffer, &p, &cmd_buffer_length);
905 	}
906 
907 	*free_buffer = (cmd_buffer != stmt->execute_cmd_buffer.buffer);
908 	*request_len = (p - cmd_buffer);
909 	*request = cmd_buffer;
910 	DBG_INF_FMT("ret=%s", ret == PASS? "PASS":"FAIL");
911 	DBG_RETURN(ret);
912 }
913 /* }}} */
914