@@ -273,6 +273,133 @@ 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_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+ size_t i = 0 ;
315+ while (i < len ) {
316+ char c = s [i ];
317+ if (c == '\'' || c == '"' ) {
318+ char quote = c ;
319+ i ++ ;
320+ while (i < len ) {
321+ if (s [i ] == quote ) {
322+ if (i + 1 < len && s [i + 1 ] == quote ) {
323+ i += 2 ;
324+ continue ;
325+ }
326+ i ++ ;
327+ break ;
328+ }
329+ i ++ ;
330+ }
331+ continue ;
332+ }
333+ if (c == '$' ) {
334+ size_t tag_start = i + 1 ;
335+ size_t p = tag_start ;
336+ while (p < len && (isalnum ((unsigned char ) s [p ]) || s [p ] == '_' )) {
337+ p ++ ;
338+ }
339+ if (p < len && s [p ] == '$' ) {
340+ size_t tag_len = p - tag_start ;
341+ size_t scan = p + 1 ;
342+ bool closed = false;
343+ while (scan + tag_len + 1 < len ) {
344+ if (s [scan ] == '$' && s [scan + tag_len + 1 ] == '$'
345+ && (tag_len == 0 || memcmp (s + scan + 1 , s + tag_start , tag_len ) == 0 )) {
346+ scan = scan + tag_len + 2 ;
347+ closed = true;
348+ break ;
349+ }
350+ scan ++ ;
351+ }
352+ if (!closed ) {
353+ return false;
354+ }
355+ i = scan ;
356+ continue ;
357+ }
358+ i ++ ;
359+ continue ;
360+ }
361+ if (c == '-' && i + 1 < len && s [i + 1 ] == '-' ) {
362+ i += 2 ;
363+ while (i < len && s [i ] != '\n' ) {
364+ i ++ ;
365+ }
366+ continue ;
367+ }
368+ if (c == '/' && i + 1 < len && s [i + 1 ] == '*' ) {
369+ i += 2 ;
370+ int cdepth = 1 ;
371+ while (i + 1 < len && cdepth > 0 ) {
372+ if (s [i ] == '/' && s [i + 1 ] == '*' ) {
373+ cdepth ++ ;
374+ i += 2 ;
375+ } else if (s [i ] == '*' && s [i + 1 ] == '/' ) {
376+ cdepth -- ;
377+ i += 2 ;
378+ } else {
379+ i ++ ;
380+ }
381+ }
382+ if (cdepth != 0 ) {
383+ return false;
384+ }
385+ continue ;
386+ }
387+ if (c == '(' ) {
388+ depth ++ ;
389+ } else if (c == ')' ) {
390+ depth -- ;
391+ if (depth < 0 ) {
392+ return false;
393+ }
394+ if (depth == 0 && i != len - 1 ) {
395+ return false;
396+ }
397+ }
398+ i ++ ;
399+ }
400+ return depth == 0 ;
401+ }
402+
276403static zend_string * _php_pgsql_trim_message (const char * message )
277404{
278405 size_t i = strlen (message );
@@ -3347,9 +3474,8 @@ PHP_FUNCTION(pg_copy_to)
33473474 pgsql_link_handle * link ;
33483475 zend_string * table_name ;
33493476 zend_string * pg_delimiter = NULL ;
3350- char * pg_null_as = "\\\\N" ;
3351- size_t pg_null_as_len = 0 ;
3352- char * query ;
3477+ char * pg_null_as = "\\N" ;
3478+ size_t pg_null_as_len = sizeof ("\\N" ) - 1 ;
33533479 PGconn * pgsql ;
33543480 PGresult * pgsql_result ;
33553481 ExecStatusType status ;
@@ -3373,14 +3499,56 @@ PHP_FUNCTION(pg_copy_to)
33733499 zend_argument_value_error (3 , "must be one character" );
33743500 RETURN_THROWS ();
33753501 }
3502+ smart_str querystr = {0 };
3503+ smart_str_appends (& querystr , "COPY " );
3504+ if (ZSTR_LEN (table_name ) > 0 && ZSTR_VAL (table_name )[0 ] == '(' ) {
3505+ if (!pgsql_copy_query_form_balanced (ZSTR_VAL (table_name ), ZSTR_LEN (table_name ))) {
3506+ php_error_docref (NULL , E_WARNING , "Invalid query source '%s': must be a single balanced parenthesised expression" , ZSTR_VAL (table_name ));
3507+ smart_str_free (& querystr );
3508+ RETURN_FALSE ;
3509+ }
3510+ smart_str_appendc (& querystr , '(' );
3511+ smart_str_append (& querystr , table_name );
3512+ smart_str_appendc (& querystr , ')' );
3513+ } else {
3514+ if (!pgsql_copy_table_name_is_simple (ZSTR_VAL (table_name ), ZSTR_LEN (table_name ))) {
3515+ php_error_docref (NULL , E_WARNING , "Invalid table_name '%s': must be a plain identifier or schema.table" , ZSTR_VAL (table_name ));
3516+ smart_str_free (& querystr );
3517+ RETURN_FALSE ;
3518+ }
3519+ if (build_tablename (& querystr , pgsql , table_name ) == FAILURE ) {
3520+ smart_str_free (& querystr );
3521+ RETURN_FALSE ;
3522+ }
3523+ }
33763524
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 );
3525+ char * escaped_delimiter = PQescapeLiteral (pgsql , ZSTR_VAL (pg_delimiter ), 1 );
3526+ if (!escaped_delimiter ) {
3527+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pgsql ));
3528+ php_error_docref (NULL , E_WARNING , "Failed to escape delimiter '%c': %s" , * ZSTR_VAL (pg_delimiter ), ZSTR_VAL (msgbuf ));
3529+ zend_string_release (msgbuf );
3530+ smart_str_free (& querystr );
3531+ RETURN_FALSE ;
3532+ }
3533+ char * escaped_null_as = PQescapeLiteral (pgsql , pg_null_as , pg_null_as_len );
3534+ if (!escaped_null_as ) {
3535+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pgsql ));
3536+ php_error_docref (NULL , E_WARNING , "Failed to escape null_as '%s': %s" , pg_null_as , ZSTR_VAL (msgbuf ));
3537+ zend_string_release (msgbuf );
3538+ PQfreemem (escaped_delimiter );
3539+ smart_str_free (& querystr );
3540+ RETURN_FALSE ;
3541+ }
3542+ smart_str_append_printf (& querystr , " TO STDOUT DELIMITER %s NULL AS %s" , escaped_delimiter , escaped_null_as );
3543+ smart_str_0 (& querystr );
3544+ PQfreemem (escaped_delimiter );
3545+ PQfreemem (escaped_null_as );
33783546
33793547 while ((pgsql_result = PQgetResult (pgsql ))) {
33803548 PQclear (pgsql_result );
33813549 }
3382- pgsql_result = PQexec (pgsql , query );
3383- efree ( query );
3550+ pgsql_result = PQexec (pgsql , ZSTR_VAL ( querystr . s ) );
3551+ smart_str_free ( & querystr );
33843552
33853553 if (pgsql_result ) {
33863554 status = PQresultStatus (pgsql_result );
@@ -3462,9 +3630,8 @@ PHP_FUNCTION(pg_copy_from)
34623630 zval * value ;
34633631 zend_string * table_name ;
34643632 zend_string * pg_delimiter = NULL ;
3465- char * pg_null_as = "\\\\N" ;
3466- size_t pg_null_as_len ;
3467- char * query ;
3633+ char * pg_null_as = "\\N" ;
3634+ size_t pg_null_as_len = sizeof ("\\N" ) - 1 ;
34683635 PGconn * pgsql ;
34693636 PGresult * pgsql_result ;
34703637 ExecStatusType status ;
@@ -3488,14 +3655,46 @@ PHP_FUNCTION(pg_copy_from)
34883655 zend_argument_value_error (4 , "must be one character" );
34893656 RETURN_THROWS ();
34903657 }
3658+ smart_str querystr = {0 };
3659+ smart_str_appends (& querystr , "COPY " );
3660+ if (!pgsql_copy_table_name_is_simple (ZSTR_VAL (table_name ), ZSTR_LEN (table_name ))) {
3661+ php_error_docref (NULL , E_WARNING , "Invalid table_name '%s': must be a plain identifier or schema.table" , ZSTR_VAL (table_name ));
3662+ smart_str_free (& querystr );
3663+ RETURN_FALSE ;
3664+ }
3665+ if (build_tablename (& querystr , pgsql , table_name ) == FAILURE ) {
3666+ smart_str_free (& querystr );
3667+ RETURN_FALSE ;
3668+ }
3669+
3670+ char * escaped_delimiter = PQescapeLiteral (pgsql , ZSTR_VAL (pg_delimiter ), 1 );
3671+ if (!escaped_delimiter ) {
3672+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pgsql ));
3673+ php_error_docref (NULL , E_WARNING , "Failed to escape delimiter '%c': %s" , * ZSTR_VAL (pg_delimiter ), ZSTR_VAL (msgbuf ));
3674+ zend_string_release (msgbuf );
3675+ smart_str_free (& querystr );
3676+ RETURN_FALSE ;
3677+ }
3678+ char * escaped_null_as = PQescapeLiteral (pgsql , pg_null_as , pg_null_as_len );
3679+ if (!escaped_null_as ) {
3680+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pgsql ));
3681+ php_error_docref (NULL , E_WARNING , "Failed to escape null_as '%s': %s" , pg_null_as , ZSTR_VAL (msgbuf ));
3682+ zend_string_release (msgbuf );
3683+ PQfreemem (escaped_delimiter );
3684+ smart_str_free (& querystr );
3685+ RETURN_FALSE ;
3686+ }
3687+ smart_str_append_printf (& querystr , " FROM STDIN DELIMITER %s NULL AS %s" , escaped_delimiter , escaped_null_as );
3688+ smart_str_0 (& querystr );
3689+ PQfreemem (escaped_delimiter );
3690+ PQfreemem (escaped_null_as );
34913691
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 );
34933692 while ((pgsql_result = PQgetResult (pgsql ))) {
34943693 PQclear (pgsql_result );
34953694 }
3496- pgsql_result = PQexec (pgsql , query );
3695+ pgsql_result = PQexec (pgsql , ZSTR_VAL ( querystr . s ) );
34973696
3498- efree ( query );
3697+ smart_str_free ( & querystr );
34993698
35003699 if (pgsql_result ) {
35013700 status = PQresultStatus (pgsql_result );
@@ -5574,7 +5773,9 @@ static inline zend_result build_tablename(smart_str *querystr, PGconn *pg_link,
55745773 } else {
55755774 char * escaped = PQescapeIdentifier (pg_link , ZSTR_VAL (table ), len );
55765775 if (escaped == NULL ) {
5577- php_error_docref (NULL , E_NOTICE , "Failed to escape table name '%s'" , ZSTR_VAL (table ));
5776+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pg_link ));
5777+ php_error_docref (NULL , E_WARNING , "Failed to escape table name '%s': %s" , ZSTR_VAL (table ), ZSTR_VAL (msgbuf ));
5778+ zend_string_release (msgbuf );
55785779 return FAILURE ;
55795780 }
55805781 smart_str_appends (querystr , escaped );
@@ -5590,7 +5791,9 @@ static inline zend_result build_tablename(smart_str *querystr, PGconn *pg_link,
55905791 } else {
55915792 char * escaped = PQescapeIdentifier (pg_link , after_dot , len );
55925793 if (escaped == NULL ) {
5593- php_error_docref (NULL , E_NOTICE , "Failed to escape table name '%s'" , ZSTR_VAL (table ));
5794+ zend_string * msgbuf = _php_pgsql_trim_message (PQerrorMessage (pg_link ));
5795+ php_error_docref (NULL , E_WARNING , "Failed to escape table name '%s': %s" , ZSTR_VAL (table ), ZSTR_VAL (msgbuf ));
5796+ zend_string_release (msgbuf );
55945797 return FAILURE ;
55955798 }
55965799 smart_str_appendc (querystr , '.' );
0 commit comments