@@ -420,10 +420,33 @@ static void append_backslashes(smart_string *str, size_t num_bs) {
420
420
}
421
421
}
422
422
423
- /* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments */
424
- static void append_win_escaped_arg (smart_string * str , char * arg ) {
423
+ /* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments and
424
+ * https://learn.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way */
425
+ const char * special_chars = "()!^\"<>&|%" ;
426
+
427
+ static BOOL is_special_character_present (const char * arg )
428
+ {
429
+ for (size_t i = 0 ; i < strlen (arg ); ++ i ) {
430
+ if (strchr (special_chars , arg [i ]) != NULL ) {
431
+ return 1 ;
432
+ }
433
+ }
434
+ return 0 ;
435
+ }
436
+
437
+ static void append_win_escaped_arg (smart_string * str , char * arg , BOOL is_cmd_argument )
438
+ {
425
439
char c ;
426
440
size_t num_bs = 0 ;
441
+ BOOL has_special_character = 0 ;
442
+
443
+ if (is_cmd_argument ) {
444
+ has_special_character = is_special_character_present (arg );
445
+ if (has_special_character ) {
446
+ /* Escape double quote with ^ if executed by cmd.exe. */
447
+ smart_string_appendc (str , '^' );
448
+ }
449
+ }
427
450
smart_string_appendc (str , '"' );
428
451
while ((c = * arg )) {
429
452
if (c == '\\' ) {
@@ -434,19 +457,72 @@ static void append_win_escaped_arg(smart_string *str, char *arg) {
434
457
num_bs = num_bs * 2 + 1 ;
435
458
}
436
459
append_backslashes (str , num_bs );
460
+ if (has_special_character && strchr (special_chars , c ) != NULL ) {
461
+ /* Escape special chars with ^ if executed by cmd.exe. */
462
+ smart_string_appendc (str , '^' );
463
+ }
437
464
smart_string_appendc (str , c );
438
465
num_bs = 0 ;
439
466
}
440
467
arg ++ ;
441
468
}
442
469
append_backslashes (str , num_bs * 2 );
470
+ if (has_special_character ) {
471
+ /* Escape double quote with ^ if executed by cmd.exe. */
472
+ smart_string_appendc (str , '^' );
473
+ }
443
474
smart_string_appendc (str , '"' );
444
475
}
445
476
477
+ static BOOL stricmp_end (const char * suffix , const char * str ) {
478
+ size_t suffix_len = strlen (suffix );
479
+ size_t str_len = strlen (str );
480
+
481
+ if (suffix_len > str_len ) {
482
+ return -1 ; /* Suffix is longer than string, cannot match. */
483
+ }
484
+
485
+ /* Compare the end of the string with the suffix, ignoring case. */
486
+ return _stricmp (str + (str_len - suffix_len ), suffix );
487
+ }
488
+
489
+ static BOOL is_executed_by_cmd (const char * prog_name )
490
+ {
491
+ /* If program name is cmd.exe, then return true. */
492
+ if (_stricmp ("cmd.exe" , prog_name ) == 0 || _stricmp ("cmd" , prog_name ) == 0
493
+ || stricmp_end ("\\cmd.exe" , prog_name ) == 0 || stricmp_end ("\\cmd" , prog_name ) == 0 ) {
494
+ return 1 ;
495
+ }
496
+
497
+ /* Find the last occurrence of the directory separator (backslash or forward slash). */
498
+ char * last_separator = strrchr (prog_name , '\\' );
499
+ char * last_separator_fwd = strrchr (prog_name , '/' );
500
+ if (last_separator_fwd && (!last_separator || last_separator < last_separator_fwd )) {
501
+ last_separator = last_separator_fwd ;
502
+ }
503
+
504
+ /* Find the last dot in the filename after the last directory separator. */
505
+ char * extension = NULL ;
506
+ if (last_separator != NULL ) {
507
+ extension = strrchr (last_separator , '.' );
508
+ } else {
509
+ extension = strrchr (prog_name , '.' );
510
+ }
511
+
512
+ if (extension == NULL || extension == prog_name ) {
513
+ /* No file extension found, it is not batch file. */
514
+ return 0 ;
515
+ }
516
+
517
+ /* Check if the file extension is ".bat" or ".cmd" which is always executed by cmd.exe. */
518
+ return (_stricmp (extension , ".bat" ) == 0 || _stricmp (extension , ".cmd" ) == 0 ) ? 1 : 0 ;
519
+ }
520
+
446
521
static char * create_win_command_from_args (HashTable * args ) {
447
522
smart_string str = {0 };
448
523
zval * arg_zv ;
449
- zend_bool is_prog_name = 1 ;
524
+ BOOL is_prog_name = 1 ;
525
+ BOOL is_cmd_execution = 0 ;
450
526
int elem_num = 0 ;
451
527
452
528
ZEND_HASH_FOREACH_VAL (args , arg_zv ) {
@@ -456,11 +532,13 @@ static char *create_win_command_from_args(HashTable *args) {
456
532
return NULL ;
457
533
}
458
534
459
- if (!is_prog_name ) {
535
+ if (is_prog_name ) {
536
+ is_cmd_execution = is_executed_by_cmd (ZSTR_VAL (arg_str ));
537
+ } else {
460
538
smart_string_appendc (& str , ' ' );
461
539
}
462
540
463
- append_win_escaped_arg (& str , ZSTR_VAL (arg_str ));
541
+ append_win_escaped_arg (& str , ZSTR_VAL (arg_str ), ! is_prog_name && is_cmd_execution );
464
542
465
543
is_prog_name = 0 ;
466
544
zend_string_release (arg_str );
0 commit comments