@@ -492,11 +492,33 @@ static void append_backslashes(smart_string *str, size_t num_bs)
492
492
}
493
493
}
494
494
495
- /* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments */
496
- static void append_win_escaped_arg (smart_string * str , char * arg )
495
+ /* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments and
496
+ * https://learn.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way */
497
+ const char * special_chars = "()!^\"<>&|%" ;
498
+
499
+ static bool is_special_character_present (const char * arg )
500
+ {
501
+ for (size_t i = 0 ; i < strlen (arg ); ++ i ) {
502
+ if (strchr (special_chars , arg [i ]) != NULL ) {
503
+ return true;
504
+ }
505
+ }
506
+ return false;
507
+ }
508
+
509
+ static void append_win_escaped_arg (smart_string * str , char * arg , bool is_cmd_argument )
497
510
{
498
511
char c ;
499
512
size_t num_bs = 0 ;
513
+ bool has_special_character = false;
514
+
515
+ if (is_cmd_argument ) {
516
+ has_special_character = is_special_character_present (arg );
517
+ if (has_special_character ) {
518
+ /* Escape double quote with ^ if executed by cmd.exe. */
519
+ smart_string_appendc (str , '^' );
520
+ }
521
+ }
500
522
smart_string_appendc (str , '"' );
501
523
while ((c = * arg )) {
502
524
if (c == '\\' ) {
@@ -507,20 +529,73 @@ static void append_win_escaped_arg(smart_string *str, char *arg)
507
529
num_bs = num_bs * 2 + 1 ;
508
530
}
509
531
append_backslashes (str , num_bs );
532
+ if (has_special_character && strchr (special_chars , c ) != NULL ) {
533
+ /* Escape special chars with ^ if executed by cmd.exe. */
534
+ smart_string_appendc (str , '^' );
535
+ }
510
536
smart_string_appendc (str , c );
511
537
num_bs = 0 ;
512
538
}
513
539
arg ++ ;
514
540
}
515
541
append_backslashes (str , num_bs * 2 );
542
+ if (has_special_character ) {
543
+ /* Escape double quote with ^ if executed by cmd.exe. */
544
+ smart_string_appendc (str , '^' );
545
+ }
516
546
smart_string_appendc (str , '"' );
517
547
}
518
548
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
+
519
593
static char * create_win_command_from_args (HashTable * args )
520
594
{
521
595
smart_string str = {0 };
522
596
zval * arg_zv ;
523
- zend_bool is_prog_name = 1 ;
597
+ bool is_prog_name = true;
598
+ bool is_cmd_execution = false;
524
599
int elem_num = 0 ;
525
600
526
601
ZEND_HASH_FOREACH_VAL (args , arg_zv ) {
@@ -530,11 +605,13 @@ static char *create_win_command_from_args(HashTable *args)
530
605
return NULL ;
531
606
}
532
607
533
- if (!is_prog_name ) {
608
+ if (is_prog_name ) {
609
+ is_cmd_execution = is_executed_by_cmd (ZSTR_VAL (arg_str ));
610
+ } else {
534
611
smart_string_appendc (& str , ' ' );
535
612
}
536
613
537
- append_win_escaped_arg (& str , ZSTR_VAL (arg_str ));
614
+ append_win_escaped_arg (& str , ZSTR_VAL (arg_str ), ! is_prog_name && is_cmd_execution );
538
615
539
616
is_prog_name = 0 ;
540
617
zend_string_release (arg_str );
0 commit comments