@@ -152,6 +152,10 @@ gboolean should_read_new_subshell_prompt;
152152/* Length of the buffer for all I/O with the subshell */
153153#define PTY_BUFFER_SIZE BUF_MEDIUM // Arbitrary; but keep it >= 80
154154
155+ /* Assume that the kernel's cooked mode buffer size might not be larger than this.
156+ * On Solaris it's 256 bytes, see ticket #4480. Shave off a few bytes, just in case. */
157+ #define COOKED_MODE_BUFFER_SIZE 250
158+
155159/*** file scope type declarations ****************************************************************/
156160
157161/* For pipes */
@@ -1306,42 +1310,103 @@ init_subshell_precmd (void)
13061310
13071311/* --------------------------------------------------------------------------------------------- */
13081312/**
1309- * Carefully quote directory name to allow entering any directory safely,
1310- * no matter what weird characters it may contain in its name.
1311- * NOTE: Treat directory name an untrusted data, don't allow it to cause
1312- * executing any commands in the shell. Escape all control characters.
1313- * Use following technique:
1314- *
1315- * printf(1) with format string containing a single conversion specifier,
1316- * "b", and an argument which contains a copy of the string passed to
1317- * subshell_name_quote() with all characters, except digits and letters,
1318- * replaced by the backslash-escape sequence \0nnn, where "nnn" is the
1319- * numeric value of the character converted to octal number.
1313+ * Carefully construct a 'cd' command that allows entering any directory safely. Two things to
1314+ * watch out for:
13201315 *
1321- * cd "`printf '%b' 'ABC\0nnnDEF\0nnnXYZ'`"
1316+ * Enter any directory safely, no matter what special bytes its name contains (special shell
1317+ * characters, control characters, non-printable characters, invalid UTF-8 etc.).
1318+ * NOTE: Treat directory name an untrusted data, don't allow it to cause executing any commands in
1319+ * the shell!
13221320 *
1323- * N.B.: Use single quotes for conversion specifier to work around
1324- * tcsh 6.20+ parser breakage, see ticket #3852 for the details.
1321+ * Keep physical lines under COOKED_MODE_BUFFER_SIZE bytes, as in some kernels the buffer for the
1322+ * tty line's cooked mode is quite small. If the directory name is longer, we have to somehow
1323+ * construct a multiline cd command.
13251324 */
13261325
13271326static GString *
1328- subshell_name_quote (const char * s )
1327+ create_cd_command (const char * s )
13291328{
13301329 GString * ret ;
13311330 const char * n ;
1332- const char * quote_cmd_start , * quote_cmd_end ;
1331+ const char * quote_cmd_start , * before_wrap , * after_wrap , * quote_cmd_end ;
1332+ const char * escape_fmt ;
1333+ int line_length ;
1334+ char buf [BUF_TINY ];
13331335
1334- if (mc_global .shell -> type == SHELL_FISH )
1336+ if (mc_global .shell -> type == SHELL_BASH || mc_global .shell -> type == SHELL_ZSH )
1337+ {
1338+ /*
1339+ * bash and zsh: Use $'...\ooo...' notation (ooo is three octal digits).
1340+ *
1341+ * Use octal because hex mode likes to go multibyte.
1342+ *
1343+ * Line wrapping (if necessary) with a trailing backslash outside of quotes.
1344+ */
1345+ quote_cmd_start = " cd $'" ;
1346+ before_wrap = "'\\" ;
1347+ after_wrap = "$'" ;
1348+ quote_cmd_end = "'" ;
1349+ escape_fmt = "\\%03o" ;
1350+ }
1351+ else if (mc_global .shell -> type == SHELL_FISH )
1352+ {
1353+ /*
1354+ * fish: Use ...\xHH... notation (HH is two hex digits).
1355+ *
1356+ * Its syntax requires that escapes go outside of quotes, and the rest is also safe there.
1357+ * Use hex because it only supports octal for low (up to octal 177 / decimal 127) bytes.
1358+ *
1359+ * Line wrapping (if necessary) with a trailing backslash.
1360+ */
1361+ quote_cmd_start = " cd " ;
1362+ before_wrap = "\\" ;
1363+ after_wrap = "" ;
1364+ quote_cmd_end = "" ;
1365+ escape_fmt = "\\x%02X" ;
1366+ }
1367+ else if (mc_global .shell -> type == SHELL_TCSH )
13351368 {
1336- quote_cmd_start = "(printf '%b' '" ;
1337- quote_cmd_end = "')" ;
1369+ /*
1370+ * tcsh: Use $'...\ooo...' notation (ooo is three octal digits).
1371+ *
1372+ * It doesn't support string contants spanning across multipline lines (a trailing
1373+ * backslash introduces a space), therefore construct the string in a tmp variable.
1374+ * Nevertheless emit a trailing backslash so it's just one line in its history.
1375+ *
1376+ * The :q modifier is needed to preserve newlines and other special chars.
1377+ *
1378+ * Note that tcsh's variables aren't binary clean, in its UTF-8 mode they are enforced
1379+ * to be valid UTF-8. So unfortunately we cannot enter every weird directory.
1380+ */
1381+ quote_cmd_start = " set _mc_newdir=$'" ;
1382+ before_wrap = "'; \\" ;
1383+ after_wrap = " set _mc_newdir=${_mc_newdir:q}$'" ;
1384+ quote_cmd_end = "'; cd ${_mc_newdir:q}" ;
1385+ escape_fmt = "\\%03o" ;
13381386 }
13391387 else
13401388 {
1341- quote_cmd_start = "\"`printf '%b' '" ;
1342- quote_cmd_end = "'`\"" ;
1389+ /*
1390+ * Fallback / POSIX sh: Construct a command like this:
1391+ *
1392+ * _mc_newdir_="`printf '%b_' 'ABC\0oooDEF\0oooXYZ'`" # ooo are three octal digits
1393+ * cd "${_mc_newdir_%_}"
1394+ *
1395+ * Command substitution removes final '\n's, hence the added and later removed '_' (#2325).
1396+ *
1397+ * Wrapping to new line with a trailing backslash outside of the innermost single quotes.
1398+ */
1399+ quote_cmd_start = " _mc_newdir_=\"`printf '%b_' '" ;
1400+ before_wrap = "'\\" ;
1401+ after_wrap = "'" ;
1402+ quote_cmd_end = "'`\"; cd \"${_mc_newdir_%_}\"" ;
1403+ escape_fmt = "\\0%03o" ;
13431404 }
13441405
1406+ /* Measure the length of an escaped byte. In the unlikely case that it won't be uniform in some
1407+ * future shell, have an upper estimate by measuring the largest byte. */
1408+ const int escaped_char_len = sprintf (buf , escape_fmt , 0xFF );
1409+
13451410 ret = g_string_sized_new (64 );
13461411
13471412 // Prevent interpreting leading '-' as a switch for 'cd'
@@ -1351,20 +1416,50 @@ subshell_name_quote (const char *s)
13511416 // Copy the beginning of the command to the buffer
13521417 g_string_append (ret , quote_cmd_start );
13531418
1354- /*
1355- * Print every character except digits and letters as a backslash-escape
1356- * sequence of the form \0nnn, where "nnn" is the numeric value of the
1357- * character converted to octal number.
1358- */
1419+ /* Sending physical lines over a certain small limit causes problems on some platforms,
1420+ * see ticket #4480. Make sure to wrap in time. See how large we can grow so that an
1421+ * additional escaped byte and the closing string still fit. */
1422+ const int max_length =
1423+ COOKED_MODE_BUFFER_SIZE - MAX (strlen (before_wrap ), strlen (quote_cmd_end ));
1424+ g_assert (max_length >= 64 ); // Make sure we have enough room to breathe.
1425+
1426+ line_length = ret -> len ;
1427+
1428+ /* Print (possibly multibyte) alphanumeric characters and '/' verbatim.
1429+ * Print everything else escaping each byte individually. */
13591430 for (const char * su = s ; su [0 ] != '\0' ; su = n )
13601431 {
13611432 n = str_cget_next_char_safe (su );
13621433
1363- if (str_isalnum (su ))
1434+ if (su [0 ] == '/' || str_isalnum (su ))
1435+ {
1436+ if (line_length + (n - su ) > max_length )
1437+ {
1438+ // wrap to next physical line
1439+ g_string_append (ret , before_wrap );
1440+ g_string_append_c (ret , '\n' );
1441+ g_string_append (ret , after_wrap );
1442+ line_length = strlen (after_wrap );
1443+ }
1444+ // append character
13641445 g_string_append_len (ret , su , (size_t ) (n - su ));
1446+ line_length += (n - su );
1447+ }
13651448 else
13661449 for (size_t c = 0 ; c < (size_t ) (n - su ); c ++ )
1367- g_string_append_printf (ret , "\\0%03o" , (unsigned char ) su [c ]);
1450+ {
1451+ if (line_length + escaped_char_len > max_length )
1452+ {
1453+ // wrap to next physical line
1454+ g_string_append (ret , before_wrap );
1455+ g_string_append_c (ret , '\n' );
1456+ g_string_append (ret , after_wrap );
1457+ line_length = strlen (after_wrap );
1458+ }
1459+ // append escaped byte
1460+ g_string_append_printf (ret , escape_fmt , (unsigned char ) su [c ]);
1461+ line_length += escaped_char_len ;
1462+ }
13681463 }
13691464
13701465 g_string_append (ret , quote_cmd_end );
@@ -1448,23 +1543,27 @@ do_subshell_chdir (const vfs_path_t *vpath, gboolean update_prompt)
14481543 feed_subshell (QUIETLY , TRUE);
14491544 }
14501545
1451- /* The initial space keeps this out of the command history (in bash
1452- because we set "HISTCONTROL=ignorespace") */
1453- write_all (mc_global .tty .subshell_pty , " cd " , 4 );
1454-
14551546 if (vpath == NULL )
1456- write_all (mc_global .tty .subshell_pty , "/" , 1 );
1547+ {
1548+ /* The initial space keeps this out of the command history (in bash
1549+ because we set "HISTCONTROL=ignorespace") */
1550+ const char * cmd = " cd /" ;
1551+ write_all (mc_global .tty .subshell_pty , cmd , sizeof (cmd ) - 1 );
1552+ }
14571553 else
14581554 {
14591555 const char * translate = vfs_translate_path (vfs_path_as_str (vpath ));
14601556
14611557 if (translate == NULL )
1462- write_all (mc_global .tty .subshell_pty , "." , 1 );
1558+ {
1559+ const char * cmd = " cd ." ;
1560+ write_all (mc_global .tty .subshell_pty , cmd , sizeof (cmd ) - 1 );
1561+ }
14631562 else
14641563 {
14651564 GString * temp ;
14661565
1467- temp = subshell_name_quote (translate );
1566+ temp = create_cd_command (translate );
14681567 write_all (mc_global .tty .subshell_pty , temp -> str , temp -> len );
14691568 g_string_free (temp , TRUE);
14701569 }
0 commit comments