@@ -538,11 +538,32 @@ static void append_backslashes(smart_str *str, size_t num_bs)
538
538
}
539
539
}
540
540
541
- /* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments */
542
- static void append_win_escaped_arg (smart_str * str , zend_string * arg )
541
+ const char * special_chars = "()!^\"<>&|%" ;
542
+
543
+ static bool is_special_character_present (const zend_string * arg )
544
+ {
545
+ for (size_t i = 0 ; i < ZSTR_LEN (arg ); ++ i ) {
546
+ if (strchr (special_chars , ZSTR_VAL (arg )[i ]) != NULL ) {
547
+ return true;
548
+ }
549
+ }
550
+ return false;
551
+ }
552
+
553
+ /* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments and
554
+ * https://learn.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way */
555
+ static void append_win_escaped_arg (smart_str * str , zend_string * arg , bool is_cmd_argument )
543
556
{
544
557
size_t num_bs = 0 ;
558
+ bool has_special_character = false;
545
559
560
+ if (is_cmd_argument ) {
561
+ has_special_character = is_special_character_present (arg );
562
+ if (has_special_character ) {
563
+ /* Escape double quote with ^ if executed by cmd.exe. */
564
+ smart_str_appendc (str , '^' );
565
+ }
566
+ }
546
567
smart_str_appendc (str , '"' );
547
568
for (size_t i = 0 ; i < ZSTR_LEN (arg ); ++ i ) {
548
569
char c = ZSTR_VAL (arg )[i ];
@@ -556,18 +577,71 @@ static void append_win_escaped_arg(smart_str *str, zend_string *arg)
556
577
num_bs = num_bs * 2 + 1 ;
557
578
}
558
579
append_backslashes (str , num_bs );
580
+ if (has_special_character && strchr (special_chars , c ) != NULL ) {
581
+ /* Escape special chars with ^ if executed by cmd.exe. */
582
+ smart_str_appendc (str , '^' );
583
+ }
559
584
smart_str_appendc (str , c );
560
585
num_bs = 0 ;
561
586
}
562
587
append_backslashes (str , num_bs * 2 );
588
+ if (has_special_character ) {
589
+ /* Escape double quote with ^ if executed by cmd.exe. */
590
+ smart_str_appendc (str , '^' );
591
+ }
563
592
smart_str_appendc (str , '"' );
564
593
}
565
594
595
+ static inline int stricmp_end (const char * suffix , const char * str ) {
596
+ size_t suffix_len = strlen (suffix );
597
+ size_t str_len = strlen (str );
598
+
599
+ if (suffix_len > str_len ) {
600
+ return -1 ; /* Suffix is longer than string, cannot match. */
601
+ }
602
+
603
+ /* Compare the end of the string with the suffix, ignoring case. */
604
+ return _stricmp (str + (str_len - suffix_len ), suffix );
605
+ }
606
+
607
+ static bool is_executed_by_cmd (const char * prog_name )
608
+ {
609
+ /* If program name is cmd.exe, then return true. */
610
+ if (_stricmp ("cmd.exe" , prog_name ) == 0 || _stricmp ("cmd" , prog_name ) == 0
611
+ || stricmp_end ("\\cmd.exe" , prog_name ) == 0 || stricmp_end ("\\cmd" , prog_name ) == 0 ) {
612
+ return true;
613
+ }
614
+
615
+ /* Find the last occurrence of the directory separator (backslash or forward slash). */
616
+ char * last_separator = strrchr (prog_name , '\\' );
617
+ char * last_separator_fwd = strrchr (prog_name , '/' );
618
+ if (last_separator_fwd && (!last_separator || last_separator < last_separator_fwd )) {
619
+ last_separator = last_separator_fwd ;
620
+ }
621
+
622
+ /* Find the last dot in the filename after the last directory separator. */
623
+ char * extension = NULL ;
624
+ if (last_separator != NULL ) {
625
+ extension = strrchr (last_separator , '.' );
626
+ } else {
627
+ extension = strrchr (prog_name , '.' );
628
+ }
629
+
630
+ if (extension == NULL || extension == prog_name ) {
631
+ /* No file extension found, it is not batch file. */
632
+ return false;
633
+ }
634
+
635
+ /* Check if the file extension is ".bat" or ".cmd" which is always executed by cmd.exe. */
636
+ return _stricmp (extension , ".bat" ) == 0 || _stricmp (extension , ".cmd" ) == 0 ;
637
+ }
638
+
566
639
static zend_string * create_win_command_from_args (HashTable * args )
567
640
{
568
641
smart_str str = {0 };
569
642
zval * arg_zv ;
570
- bool is_prog_name = 1 ;
643
+ bool is_prog_name = true;
644
+ bool is_cmd_execution = false;
571
645
int elem_num = 0 ;
572
646
573
647
ZEND_HASH_FOREACH_VAL (args , arg_zv ) {
@@ -577,11 +651,13 @@ static zend_string *create_win_command_from_args(HashTable *args)
577
651
return NULL ;
578
652
}
579
653
580
- if (!is_prog_name ) {
654
+ if (is_prog_name ) {
655
+ is_cmd_execution = is_executed_by_cmd (ZSTR_VAL (arg_str ));
656
+ } else {
581
657
smart_str_appendc (& str , ' ' );
582
658
}
583
659
584
- append_win_escaped_arg (& str , arg_str );
660
+ append_win_escaped_arg (& str , arg_str , ! is_prog_name && is_cmd_execution );
585
661
586
662
is_prog_name = 0 ;
587
663
zend_string_release (arg_str );
0 commit comments