@@ -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 */
@@ -1310,68 +1314,162 @@ init_subshell_precmd (void)
13101314
13111315/* --------------------------------------------------------------------------------------------- */
13121316/**
1313- * Carefully quote directory name to allow entering any directory safely,
1314- * no matter what weird characters it may contain in its name.
1315- * NOTE: Treat directory name an untrusted data, don't allow it to cause
1316- * executing any commands in the shell. Escape all control characters.
1317- * Use following technique:
1318- *
1319- * printf(1) with format string containing a single conversion specifier,
1320- * "b", and an argument which contains a copy of the string passed to
1321- * subshell_name_quote() with all characters, except digits and letters,
1322- * replaced by the backslash-escape sequence \0nnn, where "nnn" is the
1323- * numeric value of the character converted to octal number.
1317+ * Carefully construct a 'cd' command that allows entering any directory safely. Two things to
1318+ * watch out for:
13241319 *
1325- * cd "`printf '%b' 'ABC\0nnnDEF\0nnnXYZ'`"
1320+ * Enter any directory safely, no matter what special bytes its name contains (special shell
1321+ * characters, control characters, non-printable characters, invalid UTF-8 etc.).
1322+ * NOTE: Treat directory name as untrusted data, don't allow it to cause executing any commands in
1323+ * the shell!
13261324 *
1327- * N.B.: Use single quotes for conversion specifier to work around
1328- * tcsh 6.20+ parser breakage, see ticket #3852 for the details.
1325+ * Keep physical lines under COOKED_MODE_BUFFER_SIZE bytes, as in some kernels the buffer for the
1326+ * tty line's cooked mode is quite small. If the directory name is longer, we have to somehow
1327+ * construct a multiline cd command.
13291328 */
13301329
13311330static GString *
1332- subshell_name_quote (const char * s )
1331+ create_cd_command (const char * s )
13331332{
13341333 GString * ret ;
13351334 const char * n ;
1336- const char * quote_cmd_start , * quote_cmd_end ;
1335+ const char * quote_cmd_start , * before_wrap , * after_wrap , * quote_cmd_end ;
1336+ const char * escape_fmt ;
1337+ int line_length ;
1338+ char buf [BUF_TINY ];
13371339
1338- if (mc_global .shell -> type == SHELL_FISH )
1340+ if (mc_global .shell -> type == SHELL_BASH || mc_global .shell -> type == SHELL_ZSH )
1341+ {
1342+ /*
1343+ * bash and zsh: Use $'...\ooo...' notation (ooo is three octal digits).
1344+ *
1345+ * Use octal because hex mode likes to go multibyte.
1346+ *
1347+ * Line wrapping (if necessary) with a trailing backslash outside of quotes.
1348+ */
1349+ quote_cmd_start = " cd $'" ;
1350+ before_wrap = "'\\" ;
1351+ after_wrap = "$'" ;
1352+ quote_cmd_end = "'" ;
1353+ escape_fmt = "\\%03o" ;
1354+ }
1355+ else if (mc_global .shell -> type == SHELL_FISH )
13391356 {
1340- quote_cmd_start = "(printf '%b' '" ;
1341- quote_cmd_end = "')" ;
1357+ /*
1358+ * fish: Use ...\xHH... notation (HH is two hex digits).
1359+ *
1360+ * Its syntax requires that escapes go outside of quotes, and the rest is also safe there.
1361+ * Use hex because it only supports octal for low (up to octal 177 / decimal 127) bytes.
1362+ *
1363+ * Line wrapping (if necessary) with a trailing backslash.
1364+ */
1365+ quote_cmd_start = " cd " ;
1366+ before_wrap = "\\" ;
1367+ after_wrap = "" ;
1368+ quote_cmd_end = "" ;
1369+ escape_fmt = "\\x%02X" ;
1370+ }
1371+ else if (mc_global .shell -> type == SHELL_TCSH )
1372+ {
1373+ /*
1374+ * tcsh: Use $'...\ooo...' notation (ooo is three octal digits).
1375+ *
1376+ * It doesn't support string contants spanning across multipline lines (a trailing
1377+ * backslash introduces a space), therefore construct the string in a tmp variable.
1378+ * Nevertheless emit a trailing backslash so it's just one line in its history.
1379+ *
1380+ * The :q modifier is needed to preserve newlines and other special chars.
1381+ *
1382+ * Note that tcsh's variables aren't binary clean, in its UTF-8 mode they are enforced
1383+ * to be valid UTF-8. So unfortunately we cannot enter every weird directory.
1384+ */
1385+ quote_cmd_start = " set _mc_newdir=$'" ;
1386+ before_wrap = "'; \\" ;
1387+ after_wrap = " set _mc_newdir=${_mc_newdir:q}$'" ;
1388+ quote_cmd_end = "'; cd ${_mc_newdir:q}" ;
1389+ escape_fmt = "\\%03o" ;
13421390 }
13431391 else
13441392 {
1345- quote_cmd_start = "\"`printf '%b' '" ;
1346- quote_cmd_end = "'`\"" ;
1393+ /*
1394+ * Fallback / POSIX sh: Construct a command like this:
1395+ *
1396+ * _mc_newdir_=`printf '%b_' 'ABC\0oooDEF\0oooXYZ'` # ooo are three octal digits
1397+ * cd "${_mc_newdir_%_}"
1398+ *
1399+ * Command substitution removes final '\n's, hence the added and later removed '_' (#2325).
1400+ *
1401+ * Wrapping to new line with a trailing backslash outside of the innermost single quotes.
1402+ */
1403+ quote_cmd_start = " _mc_newdir_=`printf '%b_' '" ;
1404+ before_wrap = "'\\" ;
1405+ after_wrap = "'" ;
1406+ quote_cmd_end = "'`; cd \"${_mc_newdir_%_}\"" ;
1407+ escape_fmt = "\\0%03o" ;
13471408 }
13481409
1410+ const int quote_cmd_start_len = strlen (quote_cmd_start );
1411+ const int before_wrap_len = strlen (before_wrap );
1412+ const int after_wrap_len = strlen (after_wrap );
1413+ const int quote_cmd_end_len = strlen (quote_cmd_end );
1414+
1415+ /* Measure the length of an escaped byte. In the unlikely case that it won't be uniform in some
1416+ * future shell, have an upper estimate by measuring the largest byte. */
1417+ const int escaped_char_len = sprintf (buf , escape_fmt , 0xFF );
1418+
13491419 ret = g_string_sized_new (64 );
13501420
1421+ // Copy the beginning of the command to the buffer
1422+ g_string_append_len (ret , quote_cmd_start , quote_cmd_start_len );
1423+
13511424 // Prevent interpreting leading '-' as a switch for 'cd'
13521425 if (s [0 ] == '-' )
13531426 g_string_append (ret , "./" );
13541427
1355- // Copy the beginning of the command to the buffer
1356- g_string_append (ret , quote_cmd_start );
1428+ /* Sending physical lines over a certain small limit causes problems on some platforms,
1429+ * see ticket #4480. Make sure to wrap in time. See how large we can grow so that an
1430+ * additional line wrapping or closing string still fits. */
1431+ const int max_length = COOKED_MODE_BUFFER_SIZE - MAX (before_wrap_len , quote_cmd_end_len );
1432+ g_assert (max_length >= 64 ); // Make sure we have enough room to breathe.
13571433
1358- /*
1359- * Print every character except digits and letters as a backslash-escape
1360- * sequence of the form \0nnn, where "nnn" is the numeric value of the
1361- * character converted to octal number.
1362- */
1434+ line_length = ret -> len ;
1435+
1436+ /* Print every character except digits and letters as a backslash-escape sequence. */
13631437 for (const char * su = s ; su [0 ] != '\0' ; su = n )
13641438 {
13651439 n = str_cget_next_char_safe (su );
13661440
13671441 if (str_isalnum (su ))
1442+ {
1443+ if (line_length + (n - su ) > max_length )
1444+ {
1445+ // wrap to next physical line
1446+ g_string_append_len (ret , before_wrap , before_wrap_len );
1447+ g_string_append_c (ret , '\n' );
1448+ g_string_append_len (ret , after_wrap , after_wrap_len );
1449+ line_length = after_wrap_len ;
1450+ }
1451+ // append character
13681452 g_string_append_len (ret , su , (size_t ) (n - su ));
1453+ line_length += (n - su );
1454+ }
13691455 else
13701456 for (size_t c = 0 ; c < (size_t ) (n - su ); c ++ )
1371- g_string_append_printf (ret , "\\0%03o" , (unsigned char ) su [c ]);
1457+ {
1458+ if (line_length + escaped_char_len > max_length )
1459+ {
1460+ // wrap to next physical line
1461+ g_string_append_len (ret , before_wrap , before_wrap_len );
1462+ g_string_append_c (ret , '\n' );
1463+ g_string_append_len (ret , after_wrap , after_wrap_len );
1464+ line_length = after_wrap_len ;
1465+ }
1466+ // append escaped byte
1467+ g_string_append_printf (ret , escape_fmt , (unsigned char ) su [c ]);
1468+ line_length += escaped_char_len ;
1469+ }
13721470 }
13731471
1374- g_string_append (ret , quote_cmd_end );
1472+ g_string_append_len (ret , quote_cmd_end , quote_cmd_end_len );
13751473
13761474 return ret ;
13771475}
@@ -1452,23 +1550,27 @@ do_subshell_chdir (const vfs_path_t *vpath, gboolean update_prompt)
14521550 feed_subshell (QUIETLY , TRUE);
14531551 }
14541552
1455- /* The initial space keeps this out of the command history (in bash
1456- because we set "HISTCONTROL=ignorespace") */
1457- write_all (mc_global .tty .subshell_pty , " cd " , 4 );
1458-
14591553 if (vpath == NULL )
1460- write_all (mc_global .tty .subshell_pty , "/" , 1 );
1554+ {
1555+ /* The initial space keeps this out of the command history (in bash
1556+ because we set "HISTCONTROL=ignorespace") */
1557+ const char * cmd = " cd /" ;
1558+ write_all (mc_global .tty .subshell_pty , cmd , sizeof (cmd ) - 1 );
1559+ }
14611560 else
14621561 {
14631562 const char * translate = vfs_translate_path (vfs_path_as_str (vpath ));
14641563
14651564 if (translate == NULL )
1466- write_all (mc_global .tty .subshell_pty , "." , 1 );
1565+ {
1566+ const char * cmd = " cd ." ;
1567+ write_all (mc_global .tty .subshell_pty , cmd , sizeof (cmd ) - 1 );
1568+ }
14671569 else
14681570 {
14691571 GString * temp ;
14701572
1471- temp = subshell_name_quote (translate );
1573+ temp = create_cd_command (translate );
14721574 write_all (mc_global .tty .subshell_pty , temp -> str , temp -> len );
14731575 g_string_free (temp , TRUE);
14741576 }
0 commit comments