xref: /php-src/ext/mysqlnd/mysqlnd_ps_codec.c (revision 732d92c0)
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_JSON].func	= ps_fetch_string;
418 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_JSON].pack_len= MYSQLND_PS_SKIP_RESULT_STR;
419 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_JSON].php_type = IS_STRING;
420 
421 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].func	= ps_fetch_string;
422 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].pack_len= MYSQLND_PS_SKIP_RESULT_STR;
423 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].php_type = IS_STRING;
424 
425 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].func		= ps_fetch_string;
426 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
427 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].php_type	= IS_STRING;
428 
429 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].func		= ps_fetch_string;
430 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
431 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].php_type	= IS_STRING;
432 
433 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].func		= ps_fetch_string;
434 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
435 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].php_type	= IS_STRING;
436 
437 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_BIT].func		= ps_fetch_bit;
438 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_BIT].pack_len	= 8;
439 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_BIT].php_type	= IS_LONG;
440 
441 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].func		= ps_fetch_string;
442 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
443 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].php_type = IS_STRING;
444 
445 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_VARCHAR].func		= ps_fetch_string;
446 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_VARCHAR].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
447 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_VARCHAR].php_type	= IS_STRING;
448 
449 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_STRING].func			= ps_fetch_string;
450 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_STRING].pack_len		= MYSQLND_PS_SKIP_RESULT_STR;
451 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_STRING].php_type	= IS_STRING;
452 
453 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_DECIMAL].func		= ps_fetch_string;
454 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_DECIMAL].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
455 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_DECIMAL].php_type	= IS_STRING;
456 
457 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].func		= ps_fetch_string;
458 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
459 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].php_type	= IS_STRING;
460 
461 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_ENUM].func		= ps_fetch_string;
462 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_ENUM].pack_len	= MYSQLND_PS_SKIP_RESULT_STR;
463 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_ENUM].php_type	= IS_STRING;
464 
465 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_SET].func			= ps_fetch_string;
466 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_SET].pack_len		= MYSQLND_PS_SKIP_RESULT_STR;
467 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_SET].php_type		= IS_STRING;
468 
469 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_GEOMETRY].func	= ps_fetch_string;
470 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_GEOMETRY].pack_len= MYSQLND_PS_SKIP_RESULT_STR;
471 	mysqlnd_ps_fetch_functions[MYSQL_TYPE_GEOMETRY].php_type= IS_STRING;
472 }
473 /* }}} */
474 
475 
476 /* {{{ mysqlnd_stmt_copy_it */
477 static enum_func_status
mysqlnd_stmt_copy_it(zval ** copies,zval * original,unsigned int param_count,unsigned int current)478 mysqlnd_stmt_copy_it(zval ** copies, zval * original, unsigned int param_count, unsigned int current)
479 {
480 	if (!*copies) {
481 		*copies = mnd_ecalloc(param_count, sizeof(zval));
482 	}
483 	if (*copies) {
484 		ZVAL_COPY(&(*copies)[current], original);
485 		return PASS;
486 	}
487 	return FAIL;
488 }
489 /* }}} */
490 
491 
492 /* {{{ mysqlnd_stmt_free_copies */
493 static void
mysqlnd_stmt_free_copies(MYSQLND_STMT_DATA * stmt,zval * copies)494 mysqlnd_stmt_free_copies(MYSQLND_STMT_DATA * stmt, zval *copies)
495 {
496 	if (copies) {
497 		unsigned int i;
498 		for (i = 0; i < stmt->param_count; i++) {
499 			zval_ptr_dtor(&copies[i]);
500 		}
501 		mnd_efree(copies);
502 	}
503 }
504 /* }}} */
505 
506 
507 /* {{{ mysqlnd_stmt_execute_check_n_enlarge_buffer */
508 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)509 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)
510 {
511 	const size_t overalloc = 5;
512 	size_t left = (*buf_len - (*p - *buf));
513 
514 	if (left < (needed_bytes + overalloc)) {
515 		const size_t offset = *p - *buf;
516 		zend_uchar *tmp_buf;
517 		*buf_len = offset + needed_bytes + overalloc;
518 		tmp_buf = mnd_emalloc(*buf_len);
519 		if (!tmp_buf) {
520 			return FAIL;
521 		}
522 		memcpy(tmp_buf, *buf, offset);
523 		if (*buf != provided_buffer) {
524 			mnd_efree(*buf);
525 		}
526 		*buf = tmp_buf;
527 		/* Update our pos pointer */
528 		*p = *buf + offset;
529 	}
530 	return PASS;
531 }
532 /* }}} */
533 
534 
535 /* {{{ mysqlnd_stmt_execute_prepare_param_types */
536 static enum_func_status
mysqlnd_stmt_execute_prepare_param_types(MYSQLND_STMT_DATA * stmt,zval ** copies_param,int * resend_types_next_time)537 mysqlnd_stmt_execute_prepare_param_types(MYSQLND_STMT_DATA * stmt, zval ** copies_param, int * resend_types_next_time)
538 {
539 	unsigned int i;
540 	DBG_ENTER("mysqlnd_stmt_execute_prepare_param_types");
541 	for (i = 0; i < stmt->param_count; i++) {
542 		const short current_type = stmt->param_bind[i].type;
543 		zval *parameter = &stmt->param_bind[i].zv;
544 
545 		ZVAL_DEREF(parameter);
546 		if (!Z_ISNULL_P(parameter) && (current_type == MYSQL_TYPE_LONG || current_type == MYSQL_TYPE_LONGLONG || current_type == MYSQL_TYPE_TINY)) {
547 			/* always copy the var, because we do many conversions */
548 			if (Z_TYPE_P(parameter) != IS_LONG &&
549 				PASS != mysqlnd_stmt_copy_it(copies_param, parameter, stmt->param_count, i))
550 			{
551 				SET_OOM_ERROR(stmt->error_info);
552 				goto end;
553 			}
554 			/*
555 			  if it doesn't fit in a long send it as a string.
556 			  Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX
557 			*/
558 			if (Z_TYPE_P(parameter) != IS_LONG) {
559 				zval *tmp_data = (*copies_param && !Z_ISUNDEF((*copies_param)[i]))? &(*copies_param)[i]: parameter;
560 				/*
561 				  Because converting to double and back to long can lead
562 				  to losing precision we need second variable. Conversion to double is to see if
563 				  value is too big for a long. As said, precision could be lost.
564 				*/
565 				double d = zval_get_double(tmp_data);
566 
567 				/*
568 				  if it doesn't fit in a long send it as a string.
569 				  Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX
570 				  We do transformation here, which will be used later when sending types. The code later relies on this.
571 				*/
572 				if (d >= (double) ZEND_LONG_MAX || d < (double) ZEND_LONG_MIN) {
573 					stmt->send_types_to_server = *resend_types_next_time = 1;
574 					convert_to_string(tmp_data);
575 				} else {
576 					convert_to_long(tmp_data);
577 				}
578 			}
579 		}
580 	}
581 	DBG_RETURN(PASS);
582 end:
583 	DBG_RETURN(FAIL);
584 }
585 /* }}} */
586 
587 
588 /* {{{ mysqlnd_stmt_execute_store_types */
589 static void
mysqlnd_stmt_execute_store_types(MYSQLND_STMT_DATA * stmt,zval * copies,zend_uchar ** p)590 mysqlnd_stmt_execute_store_types(MYSQLND_STMT_DATA * stmt, zval * copies, zend_uchar ** p)
591 {
592 	unsigned int i;
593 	for (i = 0; i < stmt->param_count; i++) {
594 		short current_type = stmt->param_bind[i].type;
595 		zval *parameter = &stmt->param_bind[i].zv;
596 		/* our types are not unsigned */
597 #if SIZEOF_ZEND_LONG==8
598 		if (current_type == MYSQL_TYPE_LONG) {
599 			current_type = MYSQL_TYPE_LONGLONG;
600 		}
601 #endif
602 		ZVAL_DEREF(parameter);
603 		if (!Z_ISNULL_P(parameter) && (current_type == MYSQL_TYPE_LONG || current_type == MYSQL_TYPE_LONGLONG)) {
604 			/*
605 			  if it doesn't fit in a long send it as a string.
606 			  Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX
607 			*/
608 			if (Z_TYPE_P(parameter) != IS_LONG) {
609 				const zval *tmp_data = (copies && !Z_ISUNDEF(copies[i]))? &copies[i] : parameter;
610 				/*
611 				  In case of IS_LONG we do nothing, it is ok, in case of string, we just need to set current_type.
612 				  The actual transformation has been performed several dozens line above.
613 				*/
614 				if (Z_TYPE_P(tmp_data) == IS_STRING) {
615 					current_type = MYSQL_TYPE_VAR_STRING;
616 					/*
617 					  don't change stmt->param_bind[i].type to MYSQL_TYPE_VAR_STRING
618 					  we force convert_to_long in all cases, thus the type will be right in the next switch.
619 					  if the type is however not long, then we will do a goto in the next switch.
620 					  We want to preserve the original bind type given by the user. Thus, we do these hacks.
621 					*/
622 				}
623 			}
624 		}
625 		int2store(*p, current_type);
626 		*p+= 2;
627 	}
628 }
629 /* }}} */
630 
631 
632 /* {{{ mysqlnd_stmt_execute_calculate_param_values_size */
633 static enum_func_status
mysqlnd_stmt_execute_calculate_param_values_size(MYSQLND_STMT_DATA * stmt,zval ** copies_param,size_t * data_size)634 mysqlnd_stmt_execute_calculate_param_values_size(MYSQLND_STMT_DATA * stmt, zval ** copies_param, size_t * data_size)
635 {
636 	unsigned int i;
637 	DBG_ENTER("mysqlnd_stmt_execute_calculate_param_values_size");
638 	for (i = 0; i < stmt->param_count; i++) {
639 		unsigned short is_longlong = 0;
640 		unsigned int j;
641 		zval *bind_var, *the_var = &stmt->param_bind[i].zv;
642 
643 		bind_var = the_var;
644 		ZVAL_DEREF(the_var);
645 		if ((stmt->param_bind[i].type != MYSQL_TYPE_LONG_BLOB && Z_TYPE_P(the_var) == IS_NULL)) {
646 			continue;
647 		}
648 
649 		if (Z_ISREF_P(bind_var)) {
650 			for (j = i + 1; j < stmt->param_count; j++) {
651 				if (Z_ISREF(stmt->param_bind[j].zv) && Z_REFVAL(stmt->param_bind[j].zv) == the_var) {
652 					/* Double binding of the same zval, make a copy */
653 					if (!*copies_param || Z_ISUNDEF((*copies_param)[i])) {
654 						if (PASS != mysqlnd_stmt_copy_it(copies_param, the_var, stmt->param_count, i)) {
655 							SET_OOM_ERROR(stmt->error_info);
656 							goto end;
657 						}
658 					}
659 					break;
660 				}
661 			}
662 		}
663 
664 		switch (stmt->param_bind[i].type) {
665 			case MYSQL_TYPE_DOUBLE:
666 				*data_size += 8;
667 				if (Z_TYPE_P(the_var) != IS_DOUBLE) {
668 					if (!*copies_param || Z_ISUNDEF((*copies_param)[i])) {
669 						if (PASS != mysqlnd_stmt_copy_it(copies_param, the_var, stmt->param_count, i)) {
670 							SET_OOM_ERROR(stmt->error_info);
671 							goto end;
672 						}
673 					}
674 				}
675 				break;
676 			case MYSQL_TYPE_LONGLONG:
677 				is_longlong = 4;
678 				ZEND_FALLTHROUGH;
679 			case MYSQL_TYPE_LONG:
680 				{
681 					zval *tmp_data = (*copies_param && !Z_ISUNDEF((*copies_param)[i]))? &(*copies_param)[i]: the_var;
682 					if (Z_TYPE_P(tmp_data) == IS_STRING) {
683 						goto use_string;
684 					}
685 					convert_to_long(tmp_data);
686 				}
687 				*data_size += 4 + is_longlong;
688 				break;
689 			case MYSQL_TYPE_LONG_BLOB:
690 				if (!(stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED)) {
691 					/*
692 					  User hasn't sent anything, we will send empty string.
693 					  Empty string has length of 0, encoded in 1 byte. No real
694 					  data will follow after it.
695 					*/
696 					(*data_size)++;
697 				}
698 				break;
699 			case MYSQL_TYPE_VAR_STRING:
700 use_string:
701 				*data_size += 8; /* max 8 bytes for size */
702 				if (Z_TYPE_P(the_var) != IS_STRING) {
703 					if (!*copies_param || Z_ISUNDEF((*copies_param)[i])) {
704 						if (PASS != mysqlnd_stmt_copy_it(copies_param, the_var, stmt->param_count, i)) {
705 							SET_OOM_ERROR(stmt->error_info);
706 							goto end;
707 						}
708 					}
709 					the_var = &((*copies_param)[i]);
710 				}
711 
712 				if (!try_convert_to_string(the_var)) {
713 					goto end;
714 				}
715 				*data_size += Z_STRLEN_P(the_var);
716 				break;
717 		}
718 	}
719 	DBG_RETURN(PASS);
720 end:
721 	DBG_RETURN(FAIL);
722 }
723 /* }}} */
724 
725 
726 /* {{{ mysqlnd_stmt_execute_store_param_values */
727 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)728 mysqlnd_stmt_execute_store_param_values(MYSQLND_STMT_DATA * stmt, zval * copies, zend_uchar * buf, zend_uchar ** p, size_t null_byte_offset)
729 {
730 	unsigned int i;
731 	for (i = 0; i < stmt->param_count; i++) {
732 		zval *data, *parameter = &stmt->param_bind[i].zv;
733 
734 		ZVAL_DEREF(parameter);
735 		data = (copies && !Z_ISUNDEF(copies[i]))? &copies[i]: parameter;
736 		/* Handle long data */
737 		if (!Z_ISUNDEF_P(parameter) && Z_TYPE_P(data) == IS_NULL) {
738 			(buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7));
739 		} else {
740 			switch (stmt->param_bind[i].type) {
741 				case MYSQL_TYPE_DOUBLE:
742 					convert_to_double(data);
743 					float8store(*p, Z_DVAL_P(data));
744 					(*p) += 8;
745 					break;
746 				case MYSQL_TYPE_LONGLONG:
747 					if (Z_TYPE_P(data) == IS_STRING) {
748 						goto send_string;
749 					}
750 					/* data has alreade been converted to long */
751 					int8store(*p, Z_LVAL_P(data));
752 					(*p) += 8;
753 					break;
754 				case MYSQL_TYPE_LONG:
755 					if (Z_TYPE_P(data) == IS_STRING) {
756 						goto send_string;
757 					}
758 					/* data has alreade been converted to long */
759 					int4store(*p, Z_LVAL_P(data));
760 					(*p) += 4;
761 					break;
762 				case MYSQL_TYPE_TINY:
763 					if (Z_TYPE_P(data) == IS_STRING) {
764 						goto send_string;
765 					}
766 					int1store(*p, Z_LVAL_P(data));
767 					(*p)++;
768 					break;
769 				case MYSQL_TYPE_LONG_BLOB:
770 					if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) {
771 						stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
772 					} else {
773 						/* send_long_data() not called, send empty string */
774 						*p = php_mysqlnd_net_store_length(*p, 0);
775 					}
776 					break;
777 				case MYSQL_TYPE_VAR_STRING:
778 send_string:
779 					{
780 						const size_t len = Z_STRLEN_P(data);
781 						/* to is after p. The latter hasn't been moved */
782 						*p = php_mysqlnd_net_store_length(*p, len);
783 						memcpy(*p, Z_STRVAL_P(data), len);
784 						(*p) += len;
785 					}
786 					break;
787 				default:
788 					/* Won't happen, but set to NULL */
789 					(buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7));
790 					break;
791 			}
792 		}
793 	}
794 }
795 /* }}} */
796 
797 
798 /* {{{ mysqlnd_stmt_execute_store_params */
799 static enum_func_status
mysqlnd_stmt_execute_store_params(MYSQLND_STMT * s,zend_uchar ** buf,zend_uchar ** p,size_t * buf_len)800 mysqlnd_stmt_execute_store_params(MYSQLND_STMT * s, zend_uchar **buf, zend_uchar **p, size_t *buf_len )
801 {
802 	MYSQLND_STMT_DATA * stmt = s->data;
803 	zend_uchar * provided_buffer = *buf;
804 	size_t data_size = 0;
805 	zval *copies = NULL;/* if there are different types */
806 	enum_func_status ret = FAIL;
807 	int resend_types_next_time = 0;
808 	size_t null_byte_offset;
809 
810 	DBG_ENTER("mysqlnd_stmt_execute_store_params");
811 
812 	{
813 		unsigned int null_count = (stmt->param_count + 7) / 8;
814 		if (FAIL == mysqlnd_stmt_execute_check_n_enlarge_buffer(buf, p, buf_len, provided_buffer, null_count)) {
815 			SET_OOM_ERROR(stmt->error_info);
816 			goto end;
817 		}
818 		/* put `null` bytes */
819 		null_byte_offset = *p - *buf;
820 		memset(*p, 0, null_count);
821 		*p += null_count;
822 	}
823 
824 /* 1. Store type information */
825 	/*
826 	  check if need to send the types even if stmt->send_types_to_server is 0. This is because
827 	  if we send "i" (42) then the type will be int and the server will expect int. However, if next
828 	  time we try to send > LONG_MAX, the conversion to string will send a string and the server
829 	  won't expect it and interpret the value as 0. Thus we need to resend the types, if any such values
830 	  occur, and force resend for the next execution.
831 	*/
832 	if (FAIL == mysqlnd_stmt_execute_prepare_param_types(stmt, &copies, &resend_types_next_time)) {
833 		goto end;
834 	}
835 
836 	int1store(*p, stmt->send_types_to_server);
837 	(*p)++;
838 
839 	if (stmt->send_types_to_server) {
840 		if (FAIL == mysqlnd_stmt_execute_check_n_enlarge_buffer(buf, p, buf_len, provided_buffer, stmt->param_count * 2)) {
841 			SET_OOM_ERROR(stmt->error_info);
842 			goto end;
843 		}
844 		mysqlnd_stmt_execute_store_types(stmt, copies, p);
845 	}
846 
847 	stmt->send_types_to_server = resend_types_next_time;
848 
849 /* 2. Store data */
850 	/* 2.1 Calculate how much space we need */
851 	if (FAIL == mysqlnd_stmt_execute_calculate_param_values_size(stmt, &copies, &data_size)) {
852 		goto end;
853 	}
854 
855 	/* 2.2 Enlarge the buffer, if needed */
856 	if (FAIL == mysqlnd_stmt_execute_check_n_enlarge_buffer(buf, p, buf_len, provided_buffer, data_size)) {
857 		SET_OOM_ERROR(stmt->error_info);
858 		goto end;
859 	}
860 
861 	/* 2.3 Store the actual data */
862 	mysqlnd_stmt_execute_store_param_values(stmt, copies, *buf, p, null_byte_offset);
863 
864 	ret = PASS;
865 end:
866 	mysqlnd_stmt_free_copies(stmt, copies);
867 
868 	DBG_INF_FMT("ret=%s", ret == PASS? "PASS":"FAIL");
869 	DBG_RETURN(ret);
870 }
871 /* }}} */
872 
873 
874 /* {{{ mysqlnd_stmt_execute_generate_request */
875 enum_func_status
mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s,zend_uchar ** request,size_t * request_len,bool * free_buffer)876 mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, bool * free_buffer)
877 {
878 	MYSQLND_STMT_DATA * stmt = s->data;
879 	zend_uchar	*p = stmt->execute_cmd_buffer.buffer,
880 				*cmd_buffer = stmt->execute_cmd_buffer.buffer;
881 	size_t cmd_buffer_length = stmt->execute_cmd_buffer.length;
882 	enum_func_status ret = PASS;
883 
884 	DBG_ENTER("mysqlnd_stmt_execute_generate_request");
885 
886 	int4store(p, stmt->stmt_id);
887 	p += 4;
888 
889 	/* flags is 4 bytes, we store just 1 */
890 	int1store(p, (zend_uchar) stmt->flags);
891 	p++;
892 
893 	/* Make it all zero */
894 	int4store(p, 0);
895 
896 	int1store(p, 1); /* and send 1 for iteration count */
897 	p+= 4;
898 
899 	if (stmt->param_count != 0) {
900 	    ret = mysqlnd_stmt_execute_store_params(s, &cmd_buffer, &p, &cmd_buffer_length);
901 	}
902 
903 	*free_buffer = (cmd_buffer != stmt->execute_cmd_buffer.buffer);
904 	*request_len = (p - cmd_buffer);
905 	*request = cmd_buffer;
906 	DBG_INF_FMT("ret=%s", ret == PASS? "PASS":"FAIL");
907 	DBG_RETURN(ret);
908 }
909 /* }}} */
910