xref: /PHP-7.4/ext/pdo_odbc/odbc_stmt.c (revision 98049e8b)
1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) The PHP Group                                          |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.0 of the PHP license,       |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_0.txt.                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Author: Wez Furlong <wez@php.net>                                    |
16   +----------------------------------------------------------------------+
17 */
18 
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 
23 #include "php.h"
24 #include "php_ini.h"
25 #include "ext/standard/info.h"
26 #include "pdo/php_pdo.h"
27 #include "pdo/php_pdo_driver.h"
28 #include "php_pdo_odbc.h"
29 #include "php_pdo_odbc_int.h"
30 
31 enum pdo_odbc_conv_result {
32 	PDO_ODBC_CONV_NOT_REQUIRED,
33 	PDO_ODBC_CONV_OK,
34 	PDO_ODBC_CONV_FAIL
35 };
36 
pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt * S,SWORD sqltype)37 static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SWORD sqltype)
38 {
39 	if (!S->assume_utf8) return 0;
40 	switch (sqltype) {
41 #ifdef SQL_WCHAR
42 		case SQL_WCHAR:
43 			return 1;
44 #endif
45 #ifdef SQL_WLONGVARCHAR
46 		case SQL_WLONGVARCHAR:
47 			return 1;
48 #endif
49 #ifdef SQL_WVARCHAR
50 		case SQL_WVARCHAR:
51 			return 1;
52 #endif
53 		default:
54 			return 0;
55 	}
56 }
57 
pdo_odbc_utf82ucs2(pdo_stmt_t * stmt,int is_unicode,const char * buf,zend_ulong buflen,zend_ulong * outlen)58 static int pdo_odbc_utf82ucs2(pdo_stmt_t *stmt, int is_unicode, const char *buf,
59 	zend_ulong buflen, zend_ulong *outlen)
60 {
61 #ifdef PHP_WIN32
62 	if (is_unicode && buflen) {
63 		pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
64 		DWORD ret;
65 
66 		ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, NULL, 0);
67 		if (ret == 0) {
68 			/*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/
69 			return PDO_ODBC_CONV_FAIL;
70 		}
71 
72 		ret *= sizeof(WCHAR);
73 
74 		if (S->convbufsize <= ret) {
75 			S->convbufsize = ret + sizeof(WCHAR);
76 			S->convbuf = erealloc(S->convbuf, S->convbufsize);
77 		}
78 
79 		ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, (LPWSTR)S->convbuf, S->convbufsize / sizeof(WCHAR));
80 		if (ret == 0) {
81 			/*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/
82 			return PDO_ODBC_CONV_FAIL;
83 		}
84 
85 		ret *= sizeof(WCHAR);
86 		*outlen = ret;
87 		return PDO_ODBC_CONV_OK;
88 	}
89 #endif
90 	return PDO_ODBC_CONV_NOT_REQUIRED;
91 }
92 
pdo_odbc_ucs22utf8(pdo_stmt_t * stmt,int is_unicode,const char * buf,zend_ulong buflen,zend_ulong * outlen)93 static int pdo_odbc_ucs22utf8(pdo_stmt_t *stmt, int is_unicode, const char *buf,
94 	zend_ulong buflen, zend_ulong *outlen)
95 {
96 #ifdef PHP_WIN32
97 	if (is_unicode && buflen) {
98 		pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
99 		DWORD ret;
100 
101 		ret = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)buf, buflen/sizeof(WCHAR), NULL, 0, NULL, NULL);
102 		if (ret == 0) {
103 			return PDO_ODBC_CONV_FAIL;
104 		}
105 
106 		if (S->convbufsize <= ret) {
107 			S->convbufsize = ret + 1;
108 			S->convbuf = erealloc(S->convbuf, S->convbufsize);
109 		}
110 
111 		ret = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)buf, buflen/sizeof(WCHAR), S->convbuf, S->convbufsize, NULL, NULL);
112 		if (ret == 0) {
113 			return PDO_ODBC_CONV_FAIL;
114 		}
115 
116 		*outlen = ret;
117 		S->convbuf[*outlen] = '\0';
118 		return PDO_ODBC_CONV_OK;
119 	}
120 #endif
121 	return PDO_ODBC_CONV_NOT_REQUIRED;
122 }
123 
free_cols(pdo_stmt_t * stmt,pdo_odbc_stmt * S)124 static void free_cols(pdo_stmt_t *stmt, pdo_odbc_stmt *S)
125 {
126 	if (S->cols) {
127 		int i;
128 
129 		for (i = 0; i < S->col_count; i++) {
130 			if (S->cols[i].data) {
131 				efree(S->cols[i].data);
132 			}
133 		}
134 		efree(S->cols);
135 		S->cols = NULL;
136 		S->col_count = 0;
137 	}
138 }
139 
odbc_stmt_dtor(pdo_stmt_t * stmt)140 static int odbc_stmt_dtor(pdo_stmt_t *stmt)
141 {
142 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
143 
144 	if (S->stmt != SQL_NULL_HANDLE) {
145 		if (stmt->executed) {
146 			SQLCloseCursor(S->stmt);
147 		}
148 		SQLFreeHandle(SQL_HANDLE_STMT, S->stmt);
149 		S->stmt = SQL_NULL_HANDLE;
150 	}
151 
152 	free_cols(stmt, S);
153 	if (S->convbuf) {
154 		efree(S->convbuf);
155 	}
156 	efree(S);
157 
158 	return 1;
159 }
160 
odbc_stmt_execute(pdo_stmt_t * stmt)161 static int odbc_stmt_execute(pdo_stmt_t *stmt)
162 {
163 	RETCODE rc;
164 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
165 	char *buf = NULL;
166 	SQLLEN row_count = -1;
167 
168 	if (stmt->executed) {
169 		SQLCloseCursor(S->stmt);
170 	}
171 
172 	rc = SQLExecute(S->stmt);
173 
174 	while (rc == SQL_NEED_DATA) {
175 		struct pdo_bound_param_data *param;
176 
177 		rc = SQLParamData(S->stmt, (SQLPOINTER*)&param);
178 		if (rc == SQL_NEED_DATA) {
179 			php_stream *stm;
180 			int len;
181 			pdo_odbc_param *P;
182 			zval *parameter;
183 
184 			P = (pdo_odbc_param*)param->driver_data;
185 			if (Z_ISREF(param->parameter)) {
186 				parameter = Z_REFVAL(param->parameter);
187 			} else {
188 				parameter = &param->parameter;
189 			}
190 			if (Z_TYPE_P(parameter) != IS_RESOURCE) {
191 				/* they passed in a string */
192 				zend_ulong ulen;
193 				convert_to_string(parameter);
194 
195 				switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode,
196 							Z_STRVAL_P(parameter),
197 							Z_STRLEN_P(parameter),
198 							&ulen)) {
199 					case PDO_ODBC_CONV_NOT_REQUIRED:
200 						SQLPutData(S->stmt, Z_STRVAL_P(parameter),
201 							Z_STRLEN_P(parameter));
202 						break;
203 					case PDO_ODBC_CONV_OK:
204 						SQLPutData(S->stmt, S->convbuf, ulen);
205 						break;
206 					case PDO_ODBC_CONV_FAIL:
207 						pdo_odbc_stmt_error("error converting input string");
208 						SQLCloseCursor(S->stmt);
209 						if (buf) {
210 							efree(buf);
211 						}
212 						return 0;
213 				}
214 				continue;
215 			}
216 
217 			/* we assume that LOBs are binary and don't need charset
218 			 * conversion */
219 
220 			php_stream_from_zval_no_verify(stm, parameter);
221 			if (!stm) {
222 				/* shouldn't happen either */
223 				pdo_odbc_stmt_error("input LOB is no longer a stream");
224 				SQLCloseCursor(S->stmt);
225 				if (buf) {
226 					efree(buf);
227 				}
228 				return 0;
229 			}
230 
231 			/* now suck data from the stream and stick it into the database */
232 			if (buf == NULL) {
233 				buf = emalloc(8192);
234 			}
235 
236 			do {
237 				len = php_stream_read(stm, buf, 8192);
238 				if (len == 0) {
239 					break;
240 				}
241 				SQLPutData(S->stmt, buf, len);
242 			} while (1);
243 		}
244 	}
245 
246 	if (buf) {
247 		efree(buf);
248 	}
249 
250 	switch (rc) {
251 		case SQL_SUCCESS:
252 			break;
253 		case SQL_NO_DATA_FOUND:
254 		case SQL_SUCCESS_WITH_INFO:
255 			pdo_odbc_stmt_error("SQLExecute");
256 			break;
257 
258 		default:
259 			pdo_odbc_stmt_error("SQLExecute");
260 			return 0;
261 	}
262 
263 	SQLRowCount(S->stmt, &row_count);
264 	stmt->row_count = row_count;
265 
266 	if (S->cols == NULL) {
267 		/* do first-time-only definition of bind/mapping stuff */
268 		SQLSMALLINT colcount;
269 
270 		/* how many columns do we have ? */
271 		SQLNumResultCols(S->stmt, &colcount);
272 
273 		stmt->column_count = S->col_count = (int)colcount;
274 		S->cols = ecalloc(colcount, sizeof(pdo_odbc_column));
275 		S->going_long = 0;
276 	}
277 
278 	return 1;
279 }
280 
odbc_stmt_param_hook(pdo_stmt_t * stmt,struct pdo_bound_param_data * param,enum pdo_param_event event_type)281 static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param,
282 		enum pdo_param_event event_type)
283 {
284 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
285 	RETCODE rc;
286 	SWORD sqltype = 0, ctype = 0, scale = 0, nullable = 0;
287 	SQLULEN precision = 0;
288 	pdo_odbc_param *P;
289 	zval *parameter;
290 
291 	/* we're only interested in parameters for prepared SQL right now */
292 	if (param->is_param) {
293 
294 		switch (event_type) {
295 			case PDO_PARAM_EVT_FETCH_PRE:
296 			case PDO_PARAM_EVT_FETCH_POST:
297 			case PDO_PARAM_EVT_NORMALIZE:
298 				/* Do nothing */
299 				break;
300 
301 			case PDO_PARAM_EVT_FREE:
302 				P = param->driver_data;
303 				if (P) {
304 					efree(P);
305 				}
306 				break;
307 
308 			case PDO_PARAM_EVT_ALLOC:
309 			{
310 				/* figure out what we're doing */
311 				switch (PDO_PARAM_TYPE(param->param_type)) {
312 					case PDO_PARAM_LOB:
313 						break;
314 
315 					case PDO_PARAM_STMT:
316 						return 0;
317 
318 					default:
319 						break;
320 				}
321 
322 				rc = SQLDescribeParam(S->stmt, (SQLUSMALLINT) param->paramno+1, &sqltype, &precision, &scale, &nullable);
323 				if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
324 					/* MS Access, for instance, doesn't support SQLDescribeParam,
325 					 * so we need to guess */
326 					switch (PDO_PARAM_TYPE(param->param_type)) {
327 						case PDO_PARAM_INT:
328 							sqltype = SQL_INTEGER;
329 							break;
330 						case PDO_PARAM_LOB:
331 							sqltype = SQL_LONGVARBINARY;
332 							break;
333 						default:
334 							sqltype = SQL_LONGVARCHAR;
335 					}
336 					precision = 4000;
337 					scale = 5;
338 					nullable = 1;
339 
340 					if (param->max_value_len > 0) {
341 						precision = param->max_value_len;
342 					}
343 				}
344 				if (sqltype == SQL_BINARY || sqltype == SQL_VARBINARY || sqltype == SQL_LONGVARBINARY) {
345 					ctype = SQL_C_BINARY;
346 				} else {
347 					ctype = SQL_C_CHAR;
348 				}
349 
350 				P = emalloc(sizeof(*P));
351 				param->driver_data = P;
352 
353 				P->len = 0; /* is re-populated each EXEC_PRE */
354 				P->outbuf = NULL;
355 
356 				P->is_unicode = pdo_odbc_sqltype_is_unicode(S, sqltype);
357 				if (P->is_unicode) {
358 					/* avoid driver auto-translation: we'll do it ourselves */
359 					ctype = SQL_C_BINARY;
360 				}
361 
362 				if ((param->param_type & PDO_PARAM_INPUT_OUTPUT) == PDO_PARAM_INPUT_OUTPUT) {
363 					P->paramtype = SQL_PARAM_INPUT_OUTPUT;
364 				} else if (param->max_value_len <= 0) {
365 					P->paramtype = SQL_PARAM_INPUT;
366 				} else {
367 					P->paramtype = SQL_PARAM_OUTPUT;
368 				}
369 
370 				if (P->paramtype != SQL_PARAM_INPUT) {
371 					if (PDO_PARAM_TYPE(param->param_type) != PDO_PARAM_NULL) {
372 						/* need an explicit buffer to hold result */
373 						P->len = param->max_value_len > 0 ? param->max_value_len : precision;
374 						if (P->is_unicode) {
375 							P->len *= 2;
376 						}
377 						P->outbuf = emalloc(P->len + (P->is_unicode ? 2:1));
378 					}
379 				}
380 
381 				if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->paramtype != SQL_PARAM_INPUT) {
382 					pdo_odbc_stmt_error("Can't bind a lob for output");
383 					return 0;
384 				}
385 
386 				rc = SQLBindParameter(S->stmt, (SQLUSMALLINT) param->paramno+1,
387 						P->paramtype, ctype, sqltype, precision, scale,
388 						P->paramtype == SQL_PARAM_INPUT ?
389 							(SQLPOINTER)param :
390 							P->outbuf,
391 						P->len,
392 						&P->len
393 						);
394 
395 				if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) {
396 					return 1;
397 				}
398 				pdo_odbc_stmt_error("SQLBindParameter");
399 				return 0;
400 			}
401 
402 			case PDO_PARAM_EVT_EXEC_PRE:
403 				P = param->driver_data;
404 				if (!Z_ISREF(param->parameter)) {
405 					parameter = &param->parameter;
406 				} else {
407 					parameter = Z_REFVAL(param->parameter);
408 				}
409 
410 				if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) {
411 					if (Z_TYPE_P(parameter) == IS_RESOURCE) {
412 						php_stream *stm;
413 						php_stream_statbuf sb;
414 
415 						php_stream_from_zval_no_verify(stm, parameter);
416 
417 						if (!stm) {
418 							return 0;
419 						}
420 
421 						if (0 == php_stream_stat(stm, &sb)) {
422 							if (P->outbuf) {
423 								int len, amount;
424 								char *ptr = P->outbuf;
425 								char *end = P->outbuf + P->len;
426 
427 								P->len = 0;
428 								do {
429 									amount = end - ptr;
430 									if (amount == 0) {
431 										break;
432 									}
433 									if (amount > 8192)
434 										amount = 8192;
435 									len = php_stream_read(stm, ptr, amount);
436 									if (len == 0) {
437 										break;
438 									}
439 									ptr += len;
440 									P->len += len;
441 								} while (1);
442 
443 							} else {
444 								P->len = SQL_LEN_DATA_AT_EXEC(sb.sb.st_size);
445 							}
446 						} else {
447 							if (P->outbuf) {
448 								P->len = 0;
449 							} else {
450 								P->len = SQL_LEN_DATA_AT_EXEC(0);
451 							}
452 						}
453 					} else {
454 						convert_to_string(parameter);
455 						if (P->outbuf) {
456 							P->len = Z_STRLEN_P(parameter);
457 							memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len);
458 						} else {
459 							P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter));
460 						}
461 					}
462 				} else if (Z_TYPE_P(parameter) == IS_NULL || PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL) {
463 					P->len = SQL_NULL_DATA;
464 				} else {
465 					convert_to_string(parameter);
466 					if (P->outbuf) {
467 						zend_ulong ulen;
468 						switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode,
469 								Z_STRVAL_P(parameter),
470 								Z_STRLEN_P(parameter),
471 								&ulen)) {
472 							case PDO_ODBC_CONV_FAIL:
473 							case PDO_ODBC_CONV_NOT_REQUIRED:
474 								P->len = Z_STRLEN_P(parameter);
475 								memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len);
476 								break;
477 							case PDO_ODBC_CONV_OK:
478 								P->len = ulen;
479 								memcpy(P->outbuf, S->convbuf, P->len);
480 								break;
481 						}
482 					} else {
483 						P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter));
484 					}
485 				}
486 				return 1;
487 
488 			case PDO_PARAM_EVT_EXEC_POST:
489 				P = param->driver_data;
490 
491 				if (P->outbuf) {
492 					zend_ulong ulen;
493 					char *srcbuf;
494 					zend_ulong srclen = 0;
495 
496 					if (Z_ISREF(param->parameter)) {
497 						parameter = Z_REFVAL(param->parameter);
498 					} else {
499 						parameter = &param->parameter;
500 					}
501 					zval_ptr_dtor(parameter);
502 					ZVAL_NULL(parameter);
503 
504 					if (P->len >= 0) {
505 						switch (pdo_odbc_ucs22utf8(stmt, P->is_unicode, P->outbuf, P->len, &ulen)) {
506 							case PDO_ODBC_CONV_FAIL:
507 								/* something fishy, but allow it to come back as binary */
508 							case PDO_ODBC_CONV_NOT_REQUIRED:
509 								srcbuf = P->outbuf;
510 								srclen = P->len;
511 								break;
512 							case PDO_ODBC_CONV_OK:
513 								srcbuf = S->convbuf;
514 								srclen = ulen;
515 								break;
516 						}
517 
518 						ZVAL_NEW_STR(parameter, zend_string_alloc(srclen, 0));
519 						memcpy(Z_STRVAL_P(parameter), srcbuf, srclen);
520 						Z_STRVAL_P(parameter)[Z_STRLEN_P(parameter)] = '\0';
521 					}
522 				}
523 				return 1;
524 		}
525 	}
526 	return 1;
527 }
528 
odbc_stmt_fetch(pdo_stmt_t * stmt,enum pdo_fetch_orientation ori,zend_long offset)529 static int odbc_stmt_fetch(pdo_stmt_t *stmt,
530 	enum pdo_fetch_orientation ori, zend_long offset)
531 {
532 	RETCODE rc;
533 	SQLSMALLINT odbcori;
534 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
535 
536 	switch (ori) {
537 		case PDO_FETCH_ORI_NEXT:	odbcori = SQL_FETCH_NEXT; break;
538 		case PDO_FETCH_ORI_PRIOR:	odbcori = SQL_FETCH_PRIOR; break;
539 		case PDO_FETCH_ORI_FIRST:	odbcori = SQL_FETCH_FIRST; break;
540 		case PDO_FETCH_ORI_LAST:	odbcori = SQL_FETCH_LAST; break;
541 		case PDO_FETCH_ORI_ABS:		odbcori = SQL_FETCH_ABSOLUTE; break;
542 		case PDO_FETCH_ORI_REL:		odbcori = SQL_FETCH_RELATIVE; break;
543 		default:
544 			strcpy(stmt->error_code, "HY106");
545 			return 0;
546 	}
547 	rc = SQLFetchScroll(S->stmt, odbcori, offset);
548 
549 	if (rc == SQL_SUCCESS) {
550 		return 1;
551 	}
552 	if (rc == SQL_SUCCESS_WITH_INFO) {
553 		pdo_odbc_stmt_error("SQLFetchScroll");
554 		return 1;
555 	}
556 
557 	if (rc == SQL_NO_DATA) {
558 		/* pdo_odbc_stmt_error("SQLFetchScroll"); */
559 		return 0;
560 	}
561 
562 	pdo_odbc_stmt_error("SQLFetchScroll");
563 
564 	return 0;
565 }
566 
odbc_stmt_describe(pdo_stmt_t * stmt,int colno)567 static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno)
568 {
569 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
570 	struct pdo_column_data *col = &stmt->columns[colno];
571 	RETCODE rc;
572 	SWORD	colnamelen;
573 	SQLULEN	colsize;
574 	SQLLEN displaysize;
575 
576 	rc = SQLDescribeCol(S->stmt, colno+1, S->cols[colno].colname,
577 			sizeof(S->cols[colno].colname)-1, &colnamelen,
578 			&S->cols[colno].coltype, &colsize, NULL, NULL);
579 
580 	/* This fixes a known issue with SQL Server and (max) lengths,
581 	may affect others as well.  If we are SQL_VARCHAR,
582 	SQL_VARBINARY, or SQL_WVARCHAR (or any of the long variations)
583 	and zero is returned from colsize then consider it long */
584 	if (0 == colsize &&
585 		(S->cols[colno].coltype == SQL_VARCHAR ||
586 		 S->cols[colno].coltype == SQL_LONGVARCHAR ||
587 #ifdef SQL_WVARCHAR
588 		 S->cols[colno].coltype == SQL_WVARCHAR ||
589 #endif
590 #ifdef SQL_WLONGVARCHAR
591 		 S->cols[colno].coltype == SQL_WLONGVARCHAR ||
592 #endif
593 		 S->cols[colno].coltype == SQL_VARBINARY ||
594 		 S->cols[colno].coltype == SQL_LONGVARBINARY)) {
595 			 S->going_long = 1;
596 	}
597 
598 	if (rc != SQL_SUCCESS) {
599 		pdo_odbc_stmt_error("SQLDescribeCol");
600 		if (rc != SQL_SUCCESS_WITH_INFO) {
601 			return 0;
602 		}
603 	}
604 
605 	rc = SQLColAttribute(S->stmt, colno+1,
606 			SQL_DESC_DISPLAY_SIZE,
607 			NULL, 0, NULL, &displaysize);
608 
609 	if (rc != SQL_SUCCESS) {
610 		pdo_odbc_stmt_error("SQLColAttribute");
611 		if (rc != SQL_SUCCESS_WITH_INFO) {
612 			return 0;
613 		}
614 	}
615 	colsize = displaysize;
616 
617 	col->maxlen = S->cols[colno].datalen = colsize;
618 	col->name = zend_string_init(S->cols[colno].colname, colnamelen, 0);
619 	S->cols[colno].is_unicode = pdo_odbc_sqltype_is_unicode(S, S->cols[colno].coltype);
620 
621 	/* returning data as a string */
622 	col->param_type = PDO_PARAM_STR;
623 
624 	/* tell ODBC to put it straight into our buffer, but only if it
625 	 * isn't "long" data, and only if we haven't already bound a long
626 	 * column. */
627 	if (colsize < 256 && !S->going_long) {
628 		S->cols[colno].data = emalloc(colsize+1);
629 		S->cols[colno].is_long = 0;
630 
631 		rc = SQLBindCol(S->stmt, colno+1,
632 			S->cols[colno].is_unicode ? SQL_C_BINARY : SQL_C_CHAR,
633 			S->cols[colno].data,
634  			S->cols[colno].datalen+1, &S->cols[colno].fetched_len);
635 
636 		if (rc != SQL_SUCCESS) {
637 			pdo_odbc_stmt_error("SQLBindCol");
638 			return 0;
639 		}
640 	} else {
641 		/* allocate a smaller buffer to keep around for smaller
642 		 * "long" columns */
643 		S->cols[colno].data = emalloc(256);
644 		S->going_long = 1;
645 		S->cols[colno].is_long = 1;
646 	}
647 
648 	return 1;
649 }
650 
odbc_stmt_get_col(pdo_stmt_t * stmt,int colno,char ** ptr,zend_ulong * len,int * caller_frees)651 static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong *len, int *caller_frees)
652 {
653 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
654 	pdo_odbc_column *C = &S->cols[colno];
655 	zend_ulong ulen;
656 
657 	/* if it is a column containing "long" data, perform late binding now */
658 	if (C->is_long) {
659 		SQLLEN orig_fetched_len = SQL_NULL_DATA;
660 		zend_ulong used = 0;
661 		char *buf;
662 		RETCODE rc;
663 
664 		/* fetch it into C->data, which is allocated with a length
665 		 * of 256 bytes; if there is more to be had, we then allocate
666 		 * bigger buffer for the caller to free */
667 
668 		rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, C->data,
669  			256, &C->fetched_len);
670 		orig_fetched_len = C->fetched_len;
671 
672 		if (rc == SQL_SUCCESS && C->fetched_len < 256) {
673 			/* all the data fit into our little buffer;
674 			 * jump down to the generic bound data case */
675 			goto in_data;
676 		}
677 
678 		if (rc == SQL_SUCCESS_WITH_INFO || rc == SQL_SUCCESS) {
679 			/* this is a 'long column'
680 
681 			 read the column in 255 byte blocks until the end of the column is reached, reassembling those blocks
682 			 in order into the output buffer; 255 bytes are an optimistic assumption, since the driver may assert
683 			 more or less NUL bytes at the end; we cater to that later, if actual length information is available
684 
685 			 this loop has to work whether or not SQLGetData() provides the total column length.
686 			 calling SQLDescribeCol() or other, specifically to get the column length, then doing a single read
687 			 for that size would be slower except maybe for extremely long columns.*/
688 			char *buf2;
689 
690 			buf2 = emalloc(256);
691 			buf = estrndup(C->data, 256);
692 			used = 255; /* not 256; the driver NUL terminated the buffer */
693 
694 			do {
695 				C->fetched_len = 0;
696 				/* read block. 256 bytes => 255 bytes are actually read, the last 1 is NULL */
697 				rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, buf2, 256, &C->fetched_len);
698 
699 				/* adjust `used` in case we have length info from the driver */
700 				if (orig_fetched_len >= 0 && C->fetched_len >= 0) {
701 					SQLLEN fixed_used = orig_fetched_len - C->fetched_len;
702 					ZEND_ASSERT(fixed_used <= used + 1);
703 					used = fixed_used;
704 				}
705 
706 				/* resize output buffer and reassemble block */
707 				if (rc==SQL_SUCCESS_WITH_INFO || (rc==SQL_SUCCESS && C->fetched_len > 255)) {
708 					/* point 5, in section "Retrieving Data with SQLGetData" in http://msdn.microsoft.com/en-us/library/windows/desktop/ms715441(v=vs.85).aspx
709 					 states that if SQL_SUCCESS_WITH_INFO, fetched_len will be > 255 (greater than buf2's size)
710 					 (if a driver fails to follow that and wrote less than 255 bytes to buf2, this will AV or read garbage into buf) */
711 					buf = erealloc(buf, used + 255+1);
712 					memcpy(buf + used, buf2, 255);
713 					used = used + 255;
714 				} else if (rc==SQL_SUCCESS) {
715 					buf = erealloc(buf, used + C->fetched_len+1);
716 					memcpy(buf + used, buf2, C->fetched_len);
717 					used = used + C->fetched_len;
718 				} else {
719 					/* includes SQL_NO_DATA */
720 					break;
721 				}
722 
723 			} while (1);
724 
725 			efree(buf2);
726 
727 			/* NULL terminate the buffer once, when finished, for use with the rest of PHP */
728 			buf[used] = '\0';
729 
730 			*ptr = buf;
731 			*caller_frees = 1;
732 			*len = used;
733 			if (C->is_unicode) {
734 				goto unicode_conv;
735 			}
736 			return 1;
737 		}
738 
739 		/* something went caca */
740 		*ptr = NULL;
741 		*len = 0;
742 		return 1;
743 	}
744 
745 in_data:
746 	/* check the indicator to ensure that the data is intact */
747 	if (C->fetched_len == SQL_NULL_DATA) {
748 		/* A NULL value */
749 		*ptr = NULL;
750 		*len = 0;
751 		return 1;
752 	} else if (C->fetched_len >= 0) {
753 		/* it was stored perfectly */
754 		*ptr = C->data;
755 		*len = C->fetched_len;
756 		if (C->is_unicode) {
757 			goto unicode_conv;
758 		}
759 		return 1;
760 	} else {
761 		/* no data? */
762 		*ptr = NULL;
763 		*len = 0;
764 		return 1;
765 	}
766 
767 	unicode_conv:
768 	switch (pdo_odbc_ucs22utf8(stmt, C->is_unicode, *ptr, *len, &ulen)) {
769 		case PDO_ODBC_CONV_FAIL:
770 			/* oh well.  They can have the binary version of it */
771 		case PDO_ODBC_CONV_NOT_REQUIRED:
772 			/* shouldn't happen... */
773 			return 1;
774 
775 		case PDO_ODBC_CONV_OK:
776 			if (*caller_frees) {
777 				efree(*ptr);
778 			}
779 			*ptr = emalloc(ulen + 1);
780 			*len = ulen;
781 			memcpy(*ptr, S->convbuf, ulen+1);
782 			*caller_frees = 1;
783 			return 1;
784 	}
785 	return 1;
786 }
787 
odbc_stmt_set_param(pdo_stmt_t * stmt,zend_long attr,zval * val)788 static int odbc_stmt_set_param(pdo_stmt_t *stmt, zend_long attr, zval *val)
789 {
790 	SQLRETURN rc;
791 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
792 
793 	switch (attr) {
794 		case PDO_ATTR_CURSOR_NAME:
795 			convert_to_string(val);
796 			rc = SQLSetCursorName(S->stmt, Z_STRVAL_P(val), Z_STRLEN_P(val));
797 
798 			if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) {
799 				return 1;
800 			}
801 			pdo_odbc_stmt_error("SQLSetCursorName");
802 			return 0;
803 
804 		case PDO_ODBC_ATTR_ASSUME_UTF8:
805 			S->assume_utf8 = zval_is_true(val);
806 			return 0;
807 		default:
808 			strcpy(S->einfo.last_err_msg, "Unknown Attribute");
809 			S->einfo.what = "setAttribute";
810 			strcpy(S->einfo.last_state, "IM001");
811 			return -1;
812 	}
813 }
814 
odbc_stmt_get_attr(pdo_stmt_t * stmt,zend_long attr,zval * val)815 static int odbc_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *val)
816 {
817 	SQLRETURN rc;
818 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
819 
820 	switch (attr) {
821 		case PDO_ATTR_CURSOR_NAME:
822 		{
823 			char buf[256];
824 			SQLSMALLINT len = 0;
825 			rc = SQLGetCursorName(S->stmt, buf, sizeof(buf), &len);
826 
827 			if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) {
828 				ZVAL_STRINGL(val, buf, len);
829 				return 1;
830 			}
831 			pdo_odbc_stmt_error("SQLGetCursorName");
832 			return 0;
833 		}
834 
835 		case PDO_ODBC_ATTR_ASSUME_UTF8:
836 			ZVAL_BOOL(val, S->assume_utf8 ? 1 : 0);
837 			return 0;
838 
839 		default:
840 			strcpy(S->einfo.last_err_msg, "Unknown Attribute");
841 			S->einfo.what = "getAttribute";
842 			strcpy(S->einfo.last_state, "IM001");
843 			return -1;
844 	}
845 }
846 
odbc_stmt_next_rowset(pdo_stmt_t * stmt)847 static int odbc_stmt_next_rowset(pdo_stmt_t *stmt)
848 {
849 	SQLRETURN rc;
850 	SQLSMALLINT colcount;
851 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
852 
853 	/* NOTE: can't guarantee that output or input/output parameters
854 	 * are set until this fella returns SQL_NO_DATA, according to
855 	 * MSDN ODBC docs */
856 	rc = SQLMoreResults(S->stmt);
857 
858 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
859 		return 0;
860 	}
861 
862 	free_cols(stmt, S);
863 	/* how many columns do we have ? */
864 	SQLNumResultCols(S->stmt, &colcount);
865 	stmt->column_count = S->col_count = (int)colcount;
866 	S->cols = ecalloc(colcount, sizeof(pdo_odbc_column));
867 	S->going_long = 0;
868 
869 	return 1;
870 }
871 
odbc_stmt_close_cursor(pdo_stmt_t * stmt)872 static int odbc_stmt_close_cursor(pdo_stmt_t *stmt)
873 {
874 	SQLRETURN rc;
875 	pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
876 
877 	rc = SQLCloseCursor(S->stmt);
878 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
879 		return 0;
880 	}
881 	return 1;
882 }
883 
884 const struct pdo_stmt_methods odbc_stmt_methods = {
885 	odbc_stmt_dtor,
886 	odbc_stmt_execute,
887 	odbc_stmt_fetch,
888 	odbc_stmt_describe,
889 	odbc_stmt_get_col,
890 	odbc_stmt_param_hook,
891 	odbc_stmt_set_param,
892 	odbc_stmt_get_attr, /* get attr */
893 	NULL, /* get column meta */
894 	odbc_stmt_next_rowset,
895 	odbc_stmt_close_cursor
896 };
897