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