Hello,

The diff below adds support for bracketed paste mode to ksh. Bracketed
paste mode is a set of special escape sequences, which are employed by
many terminal emulators to allow programs run inside of them to
distinguish pasted text from typed-in text [0]. This is useful for
preventing pasted text from accidentally executing commands in the
application it was pasted to. Commonly this problem arises when copying
text from a web browser to a shell since the user may have copied
hidden text from the web page which may contain control characters such
as \n [1].

Bracketed paste mode is supported by all mainstream terminal emulators
including xterm, urxvt, and gnome-terminal. The implementation proposed
here was tested with urxvt. However, since we cannot determine in
advance whether the utilized terminal emulator supports these escape
sequences the feature must be explicitly activated using the set
builtin. I haven't tested the diff extensively yet but it is rather
simple so I don't expect any breakages. One limitation of the proposed
implementation is that in only works in emacs mode. Would be happy to
address any feedback you might have.

Greetings,
Sören

[0]: https://www.xfree86.org/4.7.0/ctlseqs.html#Bracketed%20Paste%20Mode
[1]: https://thejh.net/misc/website-terminal-copy-paste

diff --git bin/ksh/edit.c bin/ksh/edit.c
index 3089d195d..4b6d45050 100644
--- bin/ksh/edit.c
+++ bin/ksh/edit.c
@@ -150,12 +150,23 @@ x_puts(const char *s)
                shf_putc(*s++, shl_out);
 }
 
+static void
+x_paste_mode(bool onoff)
+{
+       if (!Flag(FBBRACKETPASTE))
+               return;
+
+       printf((onoff) ? BRPASTE_INT : BRPASTE_DEINT);
+       fflush(stdout);
+}
+
 bool
 x_mode(bool onoff)
 {
        static bool     x_cur_mode;
        bool            prev;
 
+       x_paste_mode(onoff);
        if (x_cur_mode == onoff)
                return x_cur_mode;
        prev = x_cur_mode;
diff --git bin/ksh/edit.h bin/ksh/edit.h
index 0b604cd64..8cc774f01 100644
--- bin/ksh/edit.h
+++ bin/ksh/edit.h
@@ -34,6 +34,12 @@ extern X_chars edchars;
 #define XCF_FULLPATH   BIT(2)  /* command completion: store full path */
 #define XCF_COMMAND_FILE (XCF_COMMAND|XCF_FILE)
 
+/* https://www.xfree86.org/4.7.0/ctlseqs.html#Bracketed%20Paste%20Mode */
+#define BRPASTE_INT    "\033[?2004h"
+#define BRPASTE_DEINT  "\033[?2004l"
+#define BRPASTE_PRE    kb_encode("^[[200~")
+#define BRPASTE_POST   kb_encode("^[[201~")
+
 /* edit.c */
 int    x_getc(void);
 void   x_flush(void);
diff --git bin/ksh/emacs.c bin/ksh/emacs.c
index 694c402ff..96136263e 100644
--- bin/ksh/emacs.c
+++ bin/ksh/emacs.c
@@ -118,6 +118,7 @@ static      char    *xmp;           /* mark pointer */
 static char    *killstack[KILLSIZE];
 static int     killsp, killtp;
 static int     x_literal_set;
+static int     x_brack_paste;
 static int     x_arg_set;
 static char    *macro_args;
 static int     prompt_skip;
@@ -203,6 +204,8 @@ static int  x_fold_lower(int);
 static int     x_fold_upper(int);
 static int     x_set_arg(int);
 static int     x_comment(int);
+static int     x_brack_paste_start(int);
+static int     x_brack_paste_end(int);
 #ifdef DEBUG
 static int     x_debug_info(int);
 #endif
@@ -260,6 +263,8 @@ static const struct x_ftab x_ftab[] = {
        { x_fold_upper,         "upcase-word",                  XF_ARG },
        { x_set_arg,            "set-arg",                      XF_NOBIND },
        { x_comment,            "comment",                      0 },
+       { x_brack_paste_start,  "bracketed-paste-start",        0 },
+       { x_brack_paste_end,    "bracketed-paste-end",          0 },
        { 0, 0, 0 },
 #ifdef DEBUG
        { x_debug_info,         "debug-info",                   0 },
@@ -316,6 +321,8 @@ x_emacs(char *buf, size_t len)
        }
 
        x_literal_set = 0;
+       x_brack_paste = 0;
+
        x_arg = -1;
        x_last_command = NULL;
        while (1) {
@@ -353,6 +360,13 @@ x_emacs(char *buf, size_t len)
                        }
                }
 
+               /* In bracketed paste mode only allow x_brack_paste_end,
+                * to quit this mode, for all other commands insert a literal. 
*/
+               if (x_brack_paste && (submatch == 1 && kmatch)) {
+                       if (kmatch->ftab->xf_func != x_brack_paste_end)
+                               submatch = 0;
+               }
+
                if (submatch == 1 && kmatch) {
                        if (kmatch->ftab->xf_func == x_ins_string &&
                            kmatch->args && !macro_args) {
@@ -1479,6 +1493,10 @@ x_init_emacs(void)
 
        TAILQ_INIT(&kblist);
 
+       /* bracketed paste mode */
+       kb_add_string(x_brack_paste_start,      NULL, BRPASTE_PRE);
+       kb_add_string(x_brack_paste_end,        NULL, BRPASTE_POST);
+
        /* man page order */
        kb_add(x_abort,                 CTRL('G'), 0);
        kb_add(x_mv_back,               CTRL('B'), 0);
@@ -1984,6 +2002,21 @@ x_comment(int c)
        return KSTD;
 }
 
+int
+x_brack_paste_start(int c)
+{
+       if (Flag(FBBRACKETPASTE))
+               x_brack_paste = 1;
+       return KSTD;
+}
+
+int
+x_brack_paste_end(int c)
+{
+       if (Flag(FBBRACKETPASTE))
+               x_brack_paste = 0;
+       return KSTD;
+}
 
 /* NAME:
  *      x_prev_histword - recover word from prev command
diff --git bin/ksh/ksh.1 bin/ksh/ksh.1
index a707a8c57..4ec4bb104 100644
--- bin/ksh/ksh.1
+++ bin/ksh/ksh.1
@@ -3595,6 +3595,9 @@ during file name generation.
 Print commands and parameter assignments when they are executed, preceded by
 the value of
 .Ev PS4 .
+.It Ic bracket-paste
+Enables bracketed paste mode, requires the associated escape sequences to be
+supported by the utilized terminal emulator.
 .It Ic bgnice
 Background jobs are run with lower priority.
 .It Ic braceexpand
diff --git bin/ksh/misc.c bin/ksh/misc.c
index 672b54164..392aa49b9 100644
--- bin/ksh/misc.c
+++ bin/ksh/misc.c
@@ -123,6 +123,9 @@ const struct option sh_options[] = {
         */
        { "allexport",  'a',            OF_ANY },
        { "braceexpand",  0,            OF_ANY }, /* non-standard */
+#ifdef EMACS
+       { "bracket-paste", 0,           OF_ANY }, /* non-standard */
+#endif
        { "bgnice",       0,            OF_ANY },
        { NULL, 'c',        OF_CMDLINE },
        { "csh-history",  0,            OF_ANY }, /* non-standard */
diff --git bin/ksh/sh.h bin/ksh/sh.h
index 93beef31d..571e24955 100644
--- bin/ksh/sh.h
+++ bin/ksh/sh.h
@@ -134,6 +134,7 @@ extern const struct option sh_options[];
 enum sh_flag {
        FEXPORT = 0,    /* -a: export all */
        FBRACEEXPAND,   /* enable {} globbing */
+       FBBRACKETPASTE, /* bracketed paste mode */
        FBGNICE,        /* bgnice */
        FCOMMAND,       /* -c: (invocation) execute specified command */
        FCSHHISTORY,    /* csh-style history enabled */

Reply via email to