diff --git a/config.c b/config.c index a12a6d64..33b4208e 100644 --- a/config.c +++ b/config.c @@ -1975,6 +1975,12 @@ void setup_config_box(struct controlbox *b, int midsession, HELPCTX(selection_clipactions), CONF_ctrlshiftcv, CONF_ctrlshiftcv_custom); + s = ctrl_getset(b, "Window/Selection", "paste", + "Control pasting of text"); + ctrl_checkbox(s, "Permit control characters in pasted text", + NO_SHORTCUT, HELPCTX(no_help), + conf_checkbox_handler, I(CONF_paste_controls)); + /* * The Window/Selection/Words panel. */ diff --git a/putty.h b/putty.h index 76ab6fb7..3ebd360b 100644 --- a/putty.h +++ b/putty.h @@ -895,6 +895,7 @@ void cleanup_exit(int); /* Selection options */ \ X(INT, NONE, mouse_is_xterm) \ X(INT, NONE, rect_select) \ + X(INT, NONE, paste_controls) \ X(INT, NONE, rawcnp) \ X(INT, NONE, rtf_paste) \ X(INT, NONE, mouse_override) \ diff --git a/settings.c b/settings.c index a51f3d58..33a0a552 100644 --- a/settings.c +++ b/settings.c @@ -673,6 +673,7 @@ void save_open_settings(void *sesskey, Conf *conf) write_setting_i(sesskey, "PasteRTF", conf_get_int(conf, CONF_rtf_paste)); write_setting_i(sesskey, "MouseIsXterm", conf_get_int(conf, CONF_mouse_is_xterm)); write_setting_i(sesskey, "RectSelect", conf_get_int(conf, CONF_rect_select)); + write_setting_i(sesskey, "PasteControls", conf_get_int(conf, CONF_paste_controls)); write_setting_i(sesskey, "MouseOverride", conf_get_int(conf, CONF_mouse_override)); for (i = 0; i < 256; i += 32) { char buf[20], buf2[256]; @@ -1088,6 +1089,7 @@ void load_open_settings(void *sesskey, Conf *conf) gppi(sesskey, "PasteRTF", 0, conf, CONF_rtf_paste); gppi(sesskey, "MouseIsXterm", 0, conf, CONF_mouse_is_xterm); gppi(sesskey, "RectSelect", 0, conf, CONF_rect_select); + gppi(sesskey, "PasteControls", 0, conf, CONF_paste_controls); gppi(sesskey, "MouseOverride", 1, conf, CONF_mouse_override); for (i = 0; i < 256; i += 32) { static const char *const defaults[] = { diff --git a/terminal.c b/terminal.c index afc9b2c2..f4902fef 100644 --- a/terminal.c +++ b/terminal.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -6145,9 +6146,21 @@ static void term_paste_callback(void *vterm) term->paste_len = 0; } +/* + * Specialist string compare function. Returns true if the buffer of + * alen wide characters starting at a has as a prefix the buffer of + * blen characters starting at b. + */ +static int wstartswith(const wchar_t *a, size_t alen, + const wchar_t *b, size_t blen) +{ + return alen >= blen && !wcsncmp(a, b, blen); +} + void term_do_paste(Terminal *term, const wchar_t *data, int len) { - const wchar_t *p, *q; + const wchar_t *p; + int paste_controls = conf_get_int(term->conf, CONF_paste_controls); /* * Pasting data into the terminal counts as a keyboard event (for @@ -6168,26 +6181,51 @@ void term_do_paste(Terminal *term, const wchar_t *data, int len) term->paste_len += 6; } - p = q = data; + p = data; while (p < data + len) { - while (p < data + len && - !(p <= data + len - sel_nl_sz && - !memcmp(p, sel_nl, sizeof(sel_nl)))) - p++; + wchar_t wc = *p++; - { - int i; - for (i = 0; i < p - q; i++) { - term->paste_buffer[term->paste_len++] = q[i]; + if (wc == sel_nl[0] && + wstartswith(p-1, data+len-(p-1), sel_nl, sel_nl_sz)) { + /* + * This is the (platform-dependent) sequence that the host + * OS uses to represent newlines in clipboard data. + * Normalise it to a press of CR. + */ + p += sel_nl_sz - 1; + wc = '\015'; + } + + if ((wc & ~(wint_t)0x9F) == 0) { + /* + * This is a control code, either in the range 0x00-0x1F + * or 0x80-0x9F. We reject all of these in pastecontrols + * mode, except for a small set of permitted ones. + */ + if (!paste_controls) { + /* In line with xterm 292, accepted control chars are: + * CR, LF, tab, backspace. (And DEL, i.e. 0x7F, but + * that's permitted by virtue of not matching the bit + * mask that got us into this if statement, so we + * don't have to permit it here. */ + static const unsigned mask = + (1<<13) | (1<<10) | (1<<9) | (1<<8); + + if (wc > 15 || !((mask >> wc) & 1)) + continue; + } + + if (wc == '\033' && term->bracketed_paste && + wstartswith(p-1, data+len-(p-1), L"\033[201~", 6)) { + /* + * Also, in bracketed-paste mode, reject the ESC + * character that begins the end-of-paste sequence. + */ + continue; } } - if (p <= data + len - sel_nl_sz && - !memcmp(p, sel_nl, sizeof(sel_nl))) { - term->paste_buffer[term->paste_len++] = '\015'; - p += sel_nl_sz; - } - q = p; + term->paste_buffer[term->paste_len++] = wc; } if (term->bracketed_paste) {