@@ -492,11 +492,32 @@ static void append_backslashes(smart_str *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_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 )
497
510
{
498
511
size_t num_bs = 0 ;
512
+ bool has_special_character = false;
499
513
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
+ }
500
521
smart_str_appendc (str , '"' );
501
522
for (size_t i = 0 ; i < ZSTR_LEN (arg ); ++ i ) {
502
523
char c = ZSTR_VAL (arg )[i ];
@@ -510,18 +531,71 @@ static void append_win_escaped_arg(smart_str *str, zend_string *arg)
510
531
num_bs = num_bs * 2 + 1 ;
511
532
}
512
533
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
+ }
513
538
smart_str_appendc (str , c );
514
539
num_bs = 0 ;
515
540
}
516
541
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
+ }
517
546
smart_str_appendc (str , '"' );
518
547
}
519
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
+
520
593
static zend_string * create_win_command_from_args (HashTable * args )
521
594
{
522
595
smart_str str = {0 };
523
596
zval * arg_zv ;
524
- bool is_prog_name = 1 ;
597
+ bool is_prog_name = true;
598
+ bool is_cmd_execution = false;
525
599
int elem_num = 0 ;
526
600
527
601
ZEND_HASH_FOREACH_VAL (args , arg_zv ) {
@@ -531,11 +605,13 @@ static zend_string *create_win_command_from_args(HashTable *args)
531
605
return NULL ;
532
606
}
533
607
534
- if (!is_prog_name ) {
608
+ if (is_prog_name ) {
609
+ is_cmd_execution = is_executed_by_cmd (ZSTR_VAL (arg_str ));
610
+ } else {
535
611
smart_str_appendc (& str , ' ' );
536
612
}
537
613
538
- append_win_escaped_arg (& str , arg_str );
614
+ append_win_escaped_arg (& str , arg_str , ! is_prog_name && is_cmd_execution );
539
615
540
616
is_prog_name = 0 ;
541
617
zend_string_release (arg_str );
0 commit comments