@@ -273,6 +273,103 @@ static void pgsql_lob_free_obj(zend_object *obj)
273273
274274/* Compatibility definitions */
275275
276+ static inline zend_result build_tablename (smart_str * querystr , PGconn * pg_link , const zend_string * table );
277+
278+ static bool pgsql_copy_query_form_balanced (const char * s , size_t len )
279+ {
280+ if (len < 2 || s [0 ] != '(' || s [len - 1 ] != ')' ) {
281+ return false;
282+ }
283+ int depth = 0 ;
284+ size_t i = 0 ;
285+ while (i < len ) {
286+ char c = s [i ];
287+ if (c == '\'' || c == '"' ) {
288+ char quote = c ;
289+ i ++ ;
290+ while (i < len ) {
291+ if (s [i ] == quote ) {
292+ if (i + 1 < len && s [i + 1 ] == quote ) {
293+ i += 2 ;
294+ continue ;
295+ }
296+ i ++ ;
297+ break ;
298+ }
299+ i ++ ;
300+ }
301+ continue ;
302+ }
303+ if (c == '$' ) {
304+ size_t tag_start = i + 1 ;
305+ size_t p = tag_start ;
306+ while (p < len && (isalnum ((unsigned char ) s [p ]) || s [p ] == '_' )) {
307+ p ++ ;
308+ }
309+ if (p < len && s [p ] == '$' ) {
310+ size_t tag_len = p - tag_start ;
311+ size_t scan = p + 1 ;
312+ bool closed = false;
313+ while (scan + tag_len + 1 < len ) {
314+ if (s [scan ] == '$' && s [scan + tag_len + 1 ] == '$'
315+ && (tag_len == 0 || memcmp (s + scan + 1 , s + tag_start , tag_len ) == 0 )) {
316+ scan = scan + tag_len + 2 ;
317+ closed = true;
318+ break ;
319+ }
320+ scan ++ ;
321+ }
322+ if (!closed ) {
323+ return false;
324+ }
325+ i = scan ;
326+ continue ;
327+ }
328+ i ++ ;
329+ continue ;
330+ }
331+ if (c == '-' && i + 1 < len && s [i + 1 ] == '-' ) {
332+ i += 2 ;
333+ while (i < len && s [i ] != '\n' ) {
334+ i ++ ;
335+ }
336+ continue ;
337+ }
338+ if (c == '/' && i + 1 < len && s [i + 1 ] == '*' ) {
339+ i += 2 ;
340+ int cdepth = 1 ;
341+ while (i + 1 < len && cdepth > 0 ) {
342+ if (s [i ] == '/' && s [i + 1 ] == '*' ) {
343+ cdepth ++ ;
344+ i += 2 ;
345+ } else if (s [i ] == '*' && s [i + 1 ] == '/' ) {
346+ cdepth -- ;
347+ i += 2 ;
348+ } else {
349+ i ++ ;
350+ }
351+ }
352+ if (cdepth != 0 ) {
353+ return false;
354+ }
355+ continue ;
356+ }
357+ if (c == '(' ) {
358+ depth ++ ;
359+ } else if (c == ')' ) {
360+ depth -- ;
361+ if (depth < 0 ) {
362+ return false;
363+ }
364+ if (depth == 0 && i != len - 1 ) {
365+ return false;
366+ }
367+ }
368+ i ++ ;
369+ }
370+ return depth == 0 ;
371+ }
372+
276373static zend_string * _php_pgsql_trim_message (const char * message )
277374{
278375 size_t i = strlen (message );
@@ -3347,9 +3444,8 @@ PHP_FUNCTION(pg_copy_to)
33473444 pgsql_link_handle * link ;
33483445 zend_string * table_name ;
33493446 zend_string * pg_delimiter = NULL ;
3350- char * pg_null_as = "\\\\N" ;
3351- size_t pg_null_as_len = 0 ;
3352- char * query ;
3447+ char * pg_null_as = "\\N" ;
3448+ size_t pg_null_as_len = sizeof ("\\N" ) - 1 ;
33533449 PGconn * pgsql ;
33543450 PGresult * pgsql_result ;
33553451 ExecStatusType status ;
@@ -3373,14 +3469,49 @@ PHP_FUNCTION(pg_copy_to)
33733469 zend_argument_value_error (3 , "must be one character" );
33743470 RETURN_THROWS ();
33753471 }
3472+ smart_str querystr = {0 };
3473+ smart_str_appends (& querystr , "COPY " );
3474+ if (ZSTR_LEN (table_name ) > 0 && ZSTR_VAL (table_name )[0 ] == '(' ) {
3475+ if (!pgsql_copy_query_form_balanced (ZSTR_VAL (table_name ), ZSTR_LEN (table_name ))) {
3476+ php_error_docref (NULL , E_WARNING , "Invalid query source '%s': must be a single balanced parenthesised expression" , ZSTR_VAL (table_name ));
3477+ smart_str_free (& querystr );
3478+ RETURN_FALSE ;
3479+ }
3480+ smart_str_appendc (& querystr , '(' );
3481+ smart_str_append (& querystr , table_name );
3482+ smart_str_appendc (& querystr , ')' );
3483+ } else if (build_tablename (& querystr , pgsql , table_name ) == FAILURE ) {
3484+ smart_str_free (& querystr );
3485+ RETURN_FALSE ;
3486+ }
33763487
3377- spprintf (& query , 0 , "COPY %s TO STDOUT DELIMITER E'%c' NULL AS E'%s'" , ZSTR_VAL (table_name ), * ZSTR_VAL (pg_delimiter ), pg_null_as );
3488+ char * escaped_delimiter = PQescapeLiteral (pgsql , ZSTR_VAL (pg_delimiter ), 1 );
3489+ if (!escaped_delimiter ) {
3490+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pgsql ));
3491+ php_error_docref (NULL , E_WARNING , "Failed to escape delimiter '%c': %s" , * ZSTR_VAL (pg_delimiter ), ZSTR_VAL (msgbuf ));
3492+ zend_string_release (msgbuf );
3493+ smart_str_free (& querystr );
3494+ RETURN_FALSE ;
3495+ }
3496+ char * escaped_null_as = PQescapeLiteral (pgsql , pg_null_as , pg_null_as_len );
3497+ if (!escaped_null_as ) {
3498+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pgsql ));
3499+ php_error_docref (NULL , E_WARNING , "Failed to escape null_as '%s': %s" , pg_null_as , ZSTR_VAL (msgbuf ));
3500+ zend_string_release (msgbuf );
3501+ PQfreemem (escaped_delimiter );
3502+ smart_str_free (& querystr );
3503+ RETURN_FALSE ;
3504+ }
3505+ smart_str_append_printf (& querystr , " TO STDOUT DELIMITER %s NULL AS %s" , escaped_delimiter , escaped_null_as );
3506+ smart_str_0 (& querystr );
3507+ PQfreemem (escaped_delimiter );
3508+ PQfreemem (escaped_null_as );
33783509
33793510 while ((pgsql_result = PQgetResult (pgsql ))) {
33803511 PQclear (pgsql_result );
33813512 }
3382- pgsql_result = PQexec (pgsql , query );
3383- efree ( query );
3513+ pgsql_result = PQexec (pgsql , ZSTR_VAL ( querystr . s ) );
3514+ smart_str_free ( & querystr );
33843515
33853516 if (pgsql_result ) {
33863517 status = PQresultStatus (pgsql_result );
@@ -3462,9 +3593,8 @@ PHP_FUNCTION(pg_copy_from)
34623593 zval * value ;
34633594 zend_string * table_name ;
34643595 zend_string * pg_delimiter = NULL ;
3465- char * pg_null_as = "\\\\N" ;
3466- size_t pg_null_as_len ;
3467- char * query ;
3596+ char * pg_null_as = "\\N" ;
3597+ size_t pg_null_as_len = sizeof ("\\N" ) - 1 ;
34683598 PGconn * pgsql ;
34693599 PGresult * pgsql_result ;
34703600 ExecStatusType status ;
@@ -3488,14 +3618,41 @@ PHP_FUNCTION(pg_copy_from)
34883618 zend_argument_value_error (4 , "must be one character" );
34893619 RETURN_THROWS ();
34903620 }
3621+ smart_str querystr = {0 };
3622+ smart_str_appends (& querystr , "COPY " );
3623+ if (build_tablename (& querystr , pgsql , table_name ) == FAILURE ) {
3624+ smart_str_free (& querystr );
3625+ RETURN_FALSE ;
3626+ }
3627+
3628+ char * escaped_delimiter = PQescapeLiteral (pgsql , ZSTR_VAL (pg_delimiter ), 1 );
3629+ if (!escaped_delimiter ) {
3630+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pgsql ));
3631+ php_error_docref (NULL , E_WARNING , "Failed to escape delimiter '%c': %s" , * ZSTR_VAL (pg_delimiter ), ZSTR_VAL (msgbuf ));
3632+ zend_string_release (msgbuf );
3633+ smart_str_free (& querystr );
3634+ RETURN_FALSE ;
3635+ }
3636+ char * escaped_null_as = PQescapeLiteral (pgsql , pg_null_as , pg_null_as_len );
3637+ if (!escaped_null_as ) {
3638+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pgsql ));
3639+ php_error_docref (NULL , E_WARNING , "Failed to escape null_as '%s': %s" , pg_null_as , ZSTR_VAL (msgbuf ));
3640+ zend_string_release (msgbuf );
3641+ PQfreemem (escaped_delimiter );
3642+ smart_str_free (& querystr );
3643+ RETURN_FALSE ;
3644+ }
3645+ smart_str_append_printf (& querystr , " FROM STDIN DELIMITER %s NULL AS %s" , escaped_delimiter , escaped_null_as );
3646+ smart_str_0 (& querystr );
3647+ PQfreemem (escaped_delimiter );
3648+ PQfreemem (escaped_null_as );
34913649
3492- spprintf (& query , 0 , "COPY %s FROM STDIN DELIMITER E'%c' NULL AS E'%s'" , ZSTR_VAL (table_name ), * ZSTR_VAL (pg_delimiter ), pg_null_as );
34933650 while ((pgsql_result = PQgetResult (pgsql ))) {
34943651 PQclear (pgsql_result );
34953652 }
3496- pgsql_result = PQexec (pgsql , query );
3653+ pgsql_result = PQexec (pgsql , ZSTR_VAL ( querystr . s ) );
34973654
3498- efree ( query );
3655+ smart_str_free ( & querystr );
34993656
35003657 if (pgsql_result ) {
35013658 status = PQresultStatus (pgsql_result );
@@ -5574,7 +5731,9 @@ static inline zend_result build_tablename(smart_str *querystr, PGconn *pg_link,
55745731 } else {
55755732 char * escaped = PQescapeIdentifier (pg_link , ZSTR_VAL (table ), len );
55765733 if (escaped == NULL ) {
5577- php_error_docref (NULL , E_NOTICE , "Failed to escape table name '%s'" , ZSTR_VAL (table ));
5734+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pg_link ));
5735+ php_error_docref (NULL , E_WARNING , "Failed to escape table name '%s': %s" , ZSTR_VAL (table ), ZSTR_VAL (msgbuf ));
5736+ zend_string_release (msgbuf );
55785737 return FAILURE ;
55795738 }
55805739 smart_str_appends (querystr , escaped );
@@ -5590,7 +5749,9 @@ static inline zend_result build_tablename(smart_str *querystr, PGconn *pg_link,
55905749 } else {
55915750 char * escaped = PQescapeIdentifier (pg_link , after_dot , len );
55925751 if (escaped == NULL ) {
5593- php_error_docref (NULL , E_NOTICE , "Failed to escape table name '%s'" , ZSTR_VAL (table ));
5752+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pg_link ));
5753+ php_error_docref (NULL , E_WARNING , "Failed to escape table name '%s': %s" , ZSTR_VAL (table ), ZSTR_VAL (msgbuf ));
5754+ zend_string_release (msgbuf );
55945755 return FAILURE ;
55955756 }
55965757 smart_str_appendc (querystr , '.' );
0 commit comments