@@ -273,6 +273,60 @@ static void pgsql_lob_free_obj(zend_object *obj)
273273
274274/* Compatibility definitions */
275275
276+ static zend_result build_tablename (smart_str * querystr , PGconn * pg_link , const zend_string * table );
277+
278+ static bool pgsql_copy_table_name_is_simple (const char * s , size_t len )
279+ {
280+ if (len == 0 ) {
281+ return false;
282+ }
283+ size_t i = 0 ;
284+ if (!(isalpha ((unsigned char ) s [i ]) || s [i ] == '_' )) {
285+ return false;
286+ }
287+ i ++ ;
288+ while (i < len && (isalnum ((unsigned char ) s [i ]) || s [i ] == '_' )) {
289+ i ++ ;
290+ }
291+ if (i == len ) {
292+ return true;
293+ }
294+ if (s [i ] != '.' ) {
295+ return false;
296+ }
297+ i ++ ;
298+ if (i >= len || !(isalpha ((unsigned char ) s [i ]) || s [i ] == '_' )) {
299+ return false;
300+ }
301+ i ++ ;
302+ while (i < len && (isalnum ((unsigned char ) s [i ]) || s [i ] == '_' )) {
303+ i ++ ;
304+ }
305+ return i == len ;
306+ }
307+
308+ static bool pgsql_copy_query_form_balanced (const char * s , size_t len )
309+ {
310+ if (len < 2 || s [0 ] != '(' || s [len - 1 ] != ')' ) {
311+ return false;
312+ }
313+ int depth = 0 ;
314+ for (size_t i = 0 ; i < len ; i ++ ) {
315+ if (s [i ] == '(' ) {
316+ depth ++ ;
317+ } else if (s [i ] == ')' ) {
318+ depth -- ;
319+ if (depth < 0 ) {
320+ return false;
321+ }
322+ if (depth == 0 && i != len - 1 ) {
323+ return false;
324+ }
325+ }
326+ }
327+ return depth == 0 ;
328+ }
329+
276330static zend_string * _php_pgsql_trim_message (const char * message )
277331{
278332 size_t i = strlen (message );
@@ -3347,9 +3401,8 @@ PHP_FUNCTION(pg_copy_to)
33473401 pgsql_link_handle * link ;
33483402 zend_string * table_name ;
33493403 zend_string * pg_delimiter = NULL ;
3350- char * pg_null_as = "\\\\N" ;
3351- size_t pg_null_as_len = 0 ;
3352- char * query ;
3404+ char * pg_null_as = "\\N" ;
3405+ size_t pg_null_as_len = sizeof ("\\N" ) - 1 ;
33533406 PGconn * pgsql ;
33543407 PGresult * pgsql_result ;
33553408 ExecStatusType status ;
@@ -3373,14 +3426,55 @@ PHP_FUNCTION(pg_copy_to)
33733426 zend_argument_value_error (3 , "must be one character" );
33743427 RETURN_THROWS ();
33753428 }
3429+ bool is_query_form = ZSTR_LEN (table_name ) > 0 && ZSTR_VAL (table_name )[0 ] == '(' ;
3430+ if (is_query_form ) {
3431+ if (!pgsql_copy_query_form_balanced (ZSTR_VAL (table_name ), ZSTR_LEN (table_name ))) {
3432+ php_error_docref (NULL , E_WARNING , "Invalid query source '%s': must be a single balanced parenthesised expression" , ZSTR_VAL (table_name ));
3433+ RETURN_FALSE ;
3434+ }
3435+ } else if (!pgsql_copy_table_name_is_simple (ZSTR_VAL (table_name ), ZSTR_LEN (table_name ))) {
3436+ php_error_docref (NULL , E_WARNING , "Invalid table_name '%s': must be a plain identifier or schema.table" , ZSTR_VAL (table_name ));
3437+ RETURN_FALSE ;
3438+ }
33763439
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 );
3440+ smart_str querystr = {0 };
3441+ smart_str_appends (& querystr , "COPY " );
3442+ if (is_query_form ) {
3443+ smart_str_appendc (& querystr , '(' );
3444+ smart_str_append (& querystr , table_name );
3445+ smart_str_appendc (& querystr , ')' );
3446+ } else if (build_tablename (& querystr , pgsql , table_name ) == FAILURE ) {
3447+ smart_str_free (& querystr );
3448+ RETURN_FALSE ;
3449+ }
3450+
3451+ char * escaped_delimiter = PQescapeLiteral (pgsql , ZSTR_VAL (pg_delimiter ), 1 );
3452+ if (!escaped_delimiter ) {
3453+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pgsql ));
3454+ php_error_docref (NULL , E_WARNING , "Failed to escape delimiter '%c': %s" , * ZSTR_VAL (pg_delimiter ), ZSTR_VAL (msgbuf ));
3455+ zend_string_release (msgbuf );
3456+ smart_str_free (& querystr );
3457+ RETURN_FALSE ;
3458+ }
3459+ char * escaped_null_as = PQescapeLiteral (pgsql , pg_null_as , pg_null_as_len );
3460+ if (!escaped_null_as ) {
3461+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pgsql ));
3462+ php_error_docref (NULL , E_WARNING , "Failed to escape null_as '%s': %s" , pg_null_as , ZSTR_VAL (msgbuf ));
3463+ zend_string_release (msgbuf );
3464+ PQfreemem (escaped_delimiter );
3465+ smart_str_free (& querystr );
3466+ RETURN_FALSE ;
3467+ }
3468+ smart_str_append_printf (& querystr , " TO STDOUT DELIMITER %s NULL AS %s" , escaped_delimiter , escaped_null_as );
3469+ smart_str_0 (& querystr );
3470+ PQfreemem (escaped_delimiter );
3471+ PQfreemem (escaped_null_as );
33783472
33793473 while ((pgsql_result = PQgetResult (pgsql ))) {
33803474 PQclear (pgsql_result );
33813475 }
3382- pgsql_result = PQexec (pgsql , query );
3383- efree ( query );
3476+ pgsql_result = PQexec (pgsql , ZSTR_VAL ( querystr . s ) );
3477+ smart_str_free ( & querystr );
33843478
33853479 if (pgsql_result ) {
33863480 status = PQresultStatus (pgsql_result );
@@ -3462,9 +3556,8 @@ PHP_FUNCTION(pg_copy_from)
34623556 zval * value ;
34633557 zend_string * table_name ;
34643558 zend_string * pg_delimiter = NULL ;
3465- char * pg_null_as = "\\\\N" ;
3466- size_t pg_null_as_len ;
3467- char * query ;
3559+ char * pg_null_as = "\\N" ;
3560+ size_t pg_null_as_len = sizeof ("\\N" ) - 1 ;
34683561 PGconn * pgsql ;
34693562 PGresult * pgsql_result ;
34703563 ExecStatusType status ;
@@ -3488,14 +3581,46 @@ PHP_FUNCTION(pg_copy_from)
34883581 zend_argument_value_error (4 , "must be one character" );
34893582 RETURN_THROWS ();
34903583 }
3584+ if (!pgsql_copy_table_name_is_simple (ZSTR_VAL (table_name ), ZSTR_LEN (table_name ))) {
3585+ php_error_docref (NULL , E_WARNING , "Invalid table_name '%s': must be a plain identifier or schema.table" , ZSTR_VAL (table_name ));
3586+ RETURN_FALSE ;
3587+ }
3588+
3589+ smart_str querystr = {0 };
3590+ smart_str_appends (& querystr , "COPY " );
3591+ if (build_tablename (& querystr , pgsql , table_name ) == FAILURE ) {
3592+ smart_str_free (& querystr );
3593+ RETURN_FALSE ;
3594+ }
3595+
3596+ char * escaped_delimiter = PQescapeLiteral (pgsql , ZSTR_VAL (pg_delimiter ), 1 );
3597+ if (!escaped_delimiter ) {
3598+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pgsql ));
3599+ php_error_docref (NULL , E_WARNING , "Failed to escape delimiter '%c': %s" , * ZSTR_VAL (pg_delimiter ), ZSTR_VAL (msgbuf ));
3600+ zend_string_release (msgbuf );
3601+ smart_str_free (& querystr );
3602+ RETURN_FALSE ;
3603+ }
3604+ char * escaped_null_as = PQescapeLiteral (pgsql , pg_null_as , pg_null_as_len );
3605+ if (!escaped_null_as ) {
3606+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pgsql ));
3607+ php_error_docref (NULL , E_WARNING , "Failed to escape null_as '%s': %s" , pg_null_as , ZSTR_VAL (msgbuf ));
3608+ zend_string_release (msgbuf );
3609+ PQfreemem (escaped_delimiter );
3610+ smart_str_free (& querystr );
3611+ RETURN_FALSE ;
3612+ }
3613+ smart_str_append_printf (& querystr , " FROM STDIN DELIMITER %s NULL AS %s" , escaped_delimiter , escaped_null_as );
3614+ smart_str_0 (& querystr );
3615+ PQfreemem (escaped_delimiter );
3616+ PQfreemem (escaped_null_as );
34913617
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 );
34933618 while ((pgsql_result = PQgetResult (pgsql ))) {
34943619 PQclear (pgsql_result );
34953620 }
3496- pgsql_result = PQexec (pgsql , query );
3621+ pgsql_result = PQexec (pgsql , ZSTR_VAL ( querystr . s ) );
34973622
3498- efree ( query );
3623+ smart_str_free ( & querystr );
34993624
35003625 if (pgsql_result ) {
35013626 status = PQresultStatus (pgsql_result );
@@ -5563,7 +5688,7 @@ static bool do_exec(smart_str *querystr, ExecStatusType expect, PGconn *pg_link,
55635688}
55645689/* }}} */
55655690
5566- static inline zend_result build_tablename (smart_str * querystr , PGconn * pg_link , const zend_string * table ) /* {{{ */
5691+ static zend_result build_tablename (smart_str * querystr , PGconn * pg_link , const zend_string * table ) /* {{{ */
55675692{
55685693 /* schema.table should be "schema"."table" */
55695694 const char * dot = memchr (ZSTR_VAL (table ), '.' , ZSTR_LEN (table ));
@@ -5574,7 +5699,9 @@ static inline zend_result build_tablename(smart_str *querystr, PGconn *pg_link,
55745699 } else {
55755700 char * escaped = PQescapeIdentifier (pg_link , ZSTR_VAL (table ), len );
55765701 if (escaped == NULL ) {
5577- php_error_docref (NULL , E_NOTICE , "Failed to escape table name '%s'" , ZSTR_VAL (table ));
5702+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pg_link ));
5703+ php_error_docref (NULL , E_WARNING , "Failed to escape table name '%s': %s" , ZSTR_VAL (table ), ZSTR_VAL (msgbuf ));
5704+ zend_string_release (msgbuf );
55785705 return FAILURE ;
55795706 }
55805707 smart_str_appends (querystr , escaped );
@@ -5590,7 +5717,9 @@ static inline zend_result build_tablename(smart_str *querystr, PGconn *pg_link,
55905717 } else {
55915718 char * escaped = PQescapeIdentifier (pg_link , after_dot , len );
55925719 if (escaped == NULL ) {
5593- php_error_docref (NULL , E_NOTICE , "Failed to escape table name '%s'" , ZSTR_VAL (table ));
5720+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pg_link ));
5721+ php_error_docref (NULL , E_WARNING , "Failed to escape table name '%s': %s" , ZSTR_VAL (table ), ZSTR_VAL (msgbuf ));
5722+ zend_string_release (msgbuf );
55945723 return FAILURE ;
55955724 }
55965725 smart_str_appendc (querystr , '.' );
0 commit comments