@@ -492,11 +492,32 @@ static void append_backslashes(smart_str *str, size_t num_bs)
492492 }
493493}
494494
495- /* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments */
496- static void append_win_escaped_arg (smart_str * str , zend_string * arg )
495+ const char * special_chars = "()!^\"<>&|%" ;
496+
497+ static bool is_special_character_present (const zend_string * arg )
498+ {
499+ for (size_t i = 0 ; i < ZSTR_LEN (arg ); ++ i ) {
500+ if (strchr (special_chars , ZSTR_VAL (arg )[i ]) != NULL ) {
501+ return true;
502+ }
503+ }
504+ return false;
505+ }
506+
507+ /* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments and
508+ * https://learn.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way */
509+ static void append_win_escaped_arg (smart_str * str , zend_string * arg , bool is_cmd_argument )
497510{
498511 size_t num_bs = 0 ;
512+ bool has_special_character = false;
499513
514+ if (is_cmd_argument ) {
515+ has_special_character = is_special_character_present (arg );
516+ if (has_special_character ) {
517+ /* Escape double quote with ^ if executed by cmd.exe. */
518+ smart_str_appendc (str , '^' );
519+ }
520+ }
500521 smart_str_appendc (str , '"' );
501522 for (size_t i = 0 ; i < ZSTR_LEN (arg ); ++ i ) {
502523 char c = ZSTR_VAL (arg )[i ];
@@ -510,18 +531,71 @@ static void append_win_escaped_arg(smart_str *str, zend_string *arg)
510531 num_bs = num_bs * 2 + 1 ;
511532 }
512533 append_backslashes (str , num_bs );
534+ if (has_special_character && strchr (special_chars , c ) != NULL ) {
535+ /* Escape special chars with ^ if executed by cmd.exe. */
536+ smart_str_appendc (str , '^' );
537+ }
513538 smart_str_appendc (str , c );
514539 num_bs = 0 ;
515540 }
516541 append_backslashes (str , num_bs * 2 );
542+ if (has_special_character ) {
543+ /* Escape double quote with ^ if executed by cmd.exe. */
544+ smart_str_appendc (str , '^' );
545+ }
517546 smart_str_appendc (str , '"' );
518547}
519548
549+ static inline int stricmp_end (const char * suffix , const char * str ) {
550+ size_t suffix_len = strlen (suffix );
551+ size_t str_len = strlen (str );
552+
553+ if (suffix_len > str_len ) {
554+ return -1 ; /* Suffix is longer than string, cannot match. */
555+ }
556+
557+ /* Compare the end of the string with the suffix, ignoring case. */
558+ return _stricmp (str + (str_len - suffix_len ), suffix );
559+ }
560+
561+ static bool is_executed_by_cmd (const char * prog_name )
562+ {
563+ /* If program name is cmd.exe, then return true. */
564+ if (_stricmp ("cmd.exe" , prog_name ) == 0 || _stricmp ("cmd" , prog_name ) == 0
565+ || stricmp_end ("\\cmd.exe" , prog_name ) == 0 || stricmp_end ("\\cmd" , prog_name ) == 0 ) {
566+ return true;
567+ }
568+
569+ /* Find the last occurrence of the directory separator (backslash or forward slash). */
570+ char * last_separator = strrchr (prog_name , '\\' );
571+ char * last_separator_fwd = strrchr (prog_name , '/' );
572+ if (last_separator_fwd && (!last_separator || last_separator < last_separator_fwd )) {
573+ last_separator = last_separator_fwd ;
574+ }
575+
576+ /* Find the last dot in the filename after the last directory separator. */
577+ char * extension = NULL ;
578+ if (last_separator != NULL ) {
579+ extension = strrchr (last_separator , '.' );
580+ } else {
581+ extension = strrchr (prog_name , '.' );
582+ }
583+
584+ if (extension == NULL || extension == prog_name ) {
585+ /* No file extension found, it is not batch file. */
586+ return false;
587+ }
588+
589+ /* Check if the file extension is ".bat" or ".cmd" which is always executed by cmd.exe. */
590+ return _stricmp (extension , ".bat" ) == 0 || _stricmp (extension , ".cmd" ) == 0 ;
591+ }
592+
520593static zend_string * create_win_command_from_args (HashTable * args )
521594{
522595 smart_str str = {0 };
523596 zval * arg_zv ;
524- bool is_prog_name = 1 ;
597+ bool is_prog_name = true;
598+ bool is_cmd_execution = false;
525599 int elem_num = 0 ;
526600
527601 ZEND_HASH_FOREACH_VAL (args , arg_zv ) {
@@ -531,11 +605,13 @@ static zend_string *create_win_command_from_args(HashTable *args)
531605 return NULL ;
532606 }
533607
534- if (!is_prog_name ) {
608+ if (is_prog_name ) {
609+ is_cmd_execution = is_executed_by_cmd (ZSTR_VAL (arg_str ));
610+ } else {
535611 smart_str_appendc (& str , ' ' );
536612 }
537613
538- append_win_escaped_arg (& str , arg_str );
614+ append_win_escaped_arg (& str , arg_str , ! is_prog_name && is_cmd_execution );
539615
540616 is_prog_name = 0 ;
541617 zend_string_release (arg_str );
0 commit comments