+++ /dev/null
-/*\r
- * ldisc.c: PuTTY line discipline. Sits between the input coming\r
- * from keypresses in the window, and the output channel leading to\r
- * the back end. Implements echo and/or local line editing,\r
- * depending on what's currently configured.\r
- */\r
-\r
-#include <stdio.h>\r
-#include <ctype.h>\r
-\r
-#include "putty.h"\r
-#include "terminal.h"\r
-#include "ldisc.h"\r
-\r
-#define ECHOING (ldisc->cfg->localecho == FORCE_ON || \\r
- (ldisc->cfg->localecho == AUTO && \\r
- (ldisc->back->ldisc(ldisc->backhandle, LD_ECHO) || \\r
- term_ldisc(ldisc->term, LD_ECHO))))\r
-#define EDITING (ldisc->cfg->localedit == FORCE_ON || \\r
- (ldisc->cfg->localedit == AUTO && \\r
- (ldisc->back->ldisc(ldisc->backhandle, LD_EDIT) || \\r
- term_ldisc(ldisc->term, LD_EDIT))))\r
-\r
-static void c_write(Ldisc ldisc, char *buf, int len)\r
-{\r
- from_backend(ldisc->frontend, 0, buf, len);\r
-}\r
-\r
-static int plen(Ldisc ldisc, unsigned char c)\r
-{\r
- if ((c >= 32 && c <= 126) || (c >= 160 && !in_utf(ldisc->term)))\r
- return 1;\r
- else if (c < 128)\r
- return 2; /* ^x for some x */\r
- else if (in_utf(ldisc->term) && c >= 0xC0)\r
- return 1; /* UTF-8 introducer character\r
- * (FIXME: combining / wide chars) */\r
- else if (in_utf(ldisc->term) && c >= 0x80 && c < 0xC0)\r
- return 0; /* UTF-8 followup character */\r
- else\r
- return 4; /* <XY> hex representation */\r
-}\r
-\r
-static void pwrite(Ldisc ldisc, unsigned char c)\r
-{\r
- if ((c >= 32 && c <= 126) ||\r
- (!in_utf(ldisc->term) && c >= 0xA0) ||\r
- (in_utf(ldisc->term) && c >= 0x80)) {\r
- c_write(ldisc, (char *)&c, 1);\r
- } else if (c < 128) {\r
- char cc[2];\r
- cc[1] = (c == 127 ? '?' : c + 0x40);\r
- cc[0] = '^';\r
- c_write(ldisc, cc, 2);\r
- } else {\r
- char cc[5];\r
- sprintf(cc, "<%02X>", c);\r
- c_write(ldisc, cc, 4);\r
- }\r
-}\r
-\r
-static int char_start(Ldisc ldisc, unsigned char c)\r
-{\r
- if (in_utf(ldisc->term))\r
- return (c < 0x80 || c >= 0xC0);\r
- else\r
- return 1;\r
-}\r
-\r
-static void bsb(Ldisc ldisc, int n)\r
-{\r
- while (n--)\r
- c_write(ldisc, "\010 \010", 3);\r
-}\r
-\r
-#define CTRL(x) (x^'@')\r
-#define KCTRL(x) ((x^'@') | 0x100)\r
-\r
-void *ldisc_create(Config *mycfg, Terminal *term,\r
- Backend *back, void *backhandle,\r
- void *frontend)\r
-{\r
- Ldisc ldisc = snew(struct ldisc_tag);\r
-\r
- ldisc->buf = NULL;\r
- ldisc->buflen = 0;\r
- ldisc->bufsiz = 0;\r
- ldisc->quotenext = 0;\r
-\r
- ldisc->cfg = mycfg;\r
- ldisc->back = back;\r
- ldisc->backhandle = backhandle;\r
- ldisc->term = term;\r
- ldisc->frontend = frontend;\r
-\r
- /* Link ourselves into the backend and the terminal */\r
- if (term)\r
- term->ldisc = ldisc;\r
- if (back)\r
- back->provide_ldisc(backhandle, ldisc);\r
-\r
- return ldisc;\r
-}\r
-\r
-void ldisc_free(void *handle)\r
-{\r
- Ldisc ldisc = (Ldisc) handle;\r
-\r
- if (ldisc->term)\r
- ldisc->term->ldisc = NULL;\r
- if (ldisc->back)\r
- ldisc->back->provide_ldisc(ldisc->backhandle, NULL);\r
- if (ldisc->buf)\r
- sfree(ldisc->buf);\r
- sfree(ldisc);\r
-}\r
-\r
-void ldisc_send(void *handle, char *buf, int len, int interactive)\r
-{\r
- Ldisc ldisc = (Ldisc) handle;\r
- int keyflag = 0;\r
- /*\r
- * Called with len=0 when the options change. We must inform\r
- * the front end in case it needs to know.\r
- */\r
- if (len == 0) {\r
- ldisc_update(ldisc->frontend, ECHOING, EDITING);\r
- return;\r
- }\r
- /*\r
- * Notify the front end that something was pressed, in case\r
- * it's depending on finding out (e.g. keypress termination for\r
- * Close On Exit). \r
- */\r
- frontend_keypress(ldisc->frontend);\r
-\r
- /*\r
- * Less than zero means null terminated special string.\r
- */\r
- if (len < 0) {\r
- len = strlen(buf);\r
- keyflag = KCTRL('@');\r
- }\r
- /*\r
- * Either perform local editing, or just send characters.\r
- */\r
- if (EDITING) {\r
- while (len--) {\r
- int c;\r
- c = (unsigned char)(*buf++) + keyflag;\r
- if (!interactive && c == '\r')\r
- c += KCTRL('@');\r
- switch (ldisc->quotenext ? ' ' : c) {\r
- /*\r
- * ^h/^?: delete, and output BSBs, to return to\r
- * last character boundary (in UTF-8 mode this may\r
- * be more than one byte)\r
- * ^w: delete, and output BSBs, to return to last\r
- * space/nonspace boundary\r
- * ^u: delete, and output BSBs, to return to BOL\r
- * ^c: Do a ^u then send a telnet IP\r
- * ^z: Do a ^u then send a telnet SUSP\r
- * ^\: Do a ^u then send a telnet ABORT\r
- * ^r: echo "^R\n" and redraw line\r
- * ^v: quote next char\r
- * ^d: if at BOL, end of file and close connection,\r
- * else send line and reset to BOL\r
- * ^m: send line-plus-\r\n and reset to BOL\r
- */\r
- case KCTRL('H'):\r
- case KCTRL('?'): /* backspace/delete */\r
- if (ldisc->buflen > 0) {\r
- do {\r
- if (ECHOING)\r
- bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));\r
- ldisc->buflen--;\r
- } while (!char_start(ldisc, ldisc->buf[ldisc->buflen]));\r
- }\r
- break;\r
- case CTRL('W'): /* delete word */\r
- while (ldisc->buflen > 0) {\r
- if (ECHOING)\r
- bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));\r
- ldisc->buflen--;\r
- if (ldisc->buflen > 0 &&\r
- isspace((unsigned char)ldisc->buf[ldisc->buflen-1]) &&\r
- !isspace((unsigned char)ldisc->buf[ldisc->buflen]))\r
- break;\r
- }\r
- break;\r
- case CTRL('U'): /* delete line */\r
- case CTRL('C'): /* Send IP */\r
- case CTRL('\\'): /* Quit */\r
- case CTRL('Z'): /* Suspend */\r
- while (ldisc->buflen > 0) {\r
- if (ECHOING)\r
- bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));\r
- ldisc->buflen--;\r
- }\r
- ldisc->back->special(ldisc->backhandle, TS_EL);\r
- /*\r
- * We don't send IP, SUSP or ABORT if the user has\r
- * configured telnet specials off! This breaks\r
- * talkers otherwise.\r
- */\r
- if (!ldisc->cfg->telnet_keyboard)\r
- goto default_case;\r
- if (c == CTRL('C'))\r
- ldisc->back->special(ldisc->backhandle, TS_IP);\r
- if (c == CTRL('Z'))\r
- ldisc->back->special(ldisc->backhandle, TS_SUSP);\r
- if (c == CTRL('\\'))\r
- ldisc->back->special(ldisc->backhandle, TS_ABORT);\r
- break;\r
- case CTRL('R'): /* redraw line */\r
- if (ECHOING) {\r
- int i;\r
- c_write(ldisc, "^R\r\n", 4);\r
- for (i = 0; i < ldisc->buflen; i++)\r
- pwrite(ldisc, ldisc->buf[i]);\r
- }\r
- break;\r
- case CTRL('V'): /* quote next char */\r
- ldisc->quotenext = TRUE;\r
- break;\r
- case CTRL('D'): /* logout or send */\r
- if (ldisc->buflen == 0) {\r
- ldisc->back->special(ldisc->backhandle, TS_EOF);\r
- } else {\r
- ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);\r
- ldisc->buflen = 0;\r
- }\r
- break;\r
- /*\r
- * This particularly hideous bit of code from RDB\r
- * allows ordinary ^M^J to do the same thing as\r
- * magic-^M when in Raw protocol. The line `case\r
- * KCTRL('M'):' is _inside_ the if block. Thus:\r
- * \r
- * - receiving regular ^M goes straight to the\r
- * default clause and inserts as a literal ^M.\r
- * - receiving regular ^J _not_ directly after a\r
- * literal ^M (or not in Raw protocol) fails the\r
- * if condition, leaps to the bottom of the if,\r
- * and falls through into the default clause\r
- * again.\r
- * - receiving regular ^J just after a literal ^M\r
- * in Raw protocol passes the if condition,\r
- * deletes the literal ^M, and falls through\r
- * into the magic-^M code\r
- * - receiving a magic-^M empties the line buffer,\r
- * signals end-of-line in one of the various\r
- * entertaining ways, and _doesn't_ fall out of\r
- * the bottom of the if and through to the\r
- * default clause because of the break.\r
- */\r
- case CTRL('J'):\r
- if (ldisc->cfg->protocol == PROT_RAW &&\r
- ldisc->buflen > 0 && ldisc->buf[ldisc->buflen - 1] == '\r') {\r
- if (ECHOING)\r
- bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));\r
- ldisc->buflen--;\r
- /* FALLTHROUGH */\r
- case KCTRL('M'): /* send with newline */\r
- if (ldisc->buflen > 0)\r
- ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);\r
- if (ldisc->cfg->protocol == PROT_RAW)\r
- ldisc->back->send(ldisc->backhandle, "\r\n", 2);\r
- else if (ldisc->cfg->protocol == PROT_TELNET && ldisc->cfg->telnet_newline)\r
- ldisc->back->special(ldisc->backhandle, TS_EOL);\r
- else\r
- ldisc->back->send(ldisc->backhandle, "\r", 1);\r
- if (ECHOING)\r
- c_write(ldisc, "\r\n", 2);\r
- ldisc->buflen = 0;\r
- break;\r
- }\r
- /* FALLTHROUGH */\r
- default: /* get to this label from ^V handler */\r
- default_case:\r
- if (ldisc->buflen >= ldisc->bufsiz) {\r
- ldisc->bufsiz = ldisc->buflen + 256;\r
- ldisc->buf = sresize(ldisc->buf, ldisc->bufsiz, char);\r
- }\r
- ldisc->buf[ldisc->buflen++] = c;\r
- if (ECHOING)\r
- pwrite(ldisc, (unsigned char) c);\r
- ldisc->quotenext = FALSE;\r
- break;\r
- }\r
- }\r
- } else {\r
- if (ldisc->buflen != 0) {\r
- ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);\r
- while (ldisc->buflen > 0) {\r
- bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));\r
- ldisc->buflen--;\r
- }\r
- }\r
- if (len > 0) {\r
- if (ECHOING)\r
- c_write(ldisc, buf, len);\r
- if (keyflag && ldisc->cfg->protocol == PROT_TELNET && len == 1) {\r
- switch (buf[0]) {\r
- case CTRL('M'):\r
- if (ldisc->cfg->protocol == PROT_TELNET && ldisc->cfg->telnet_newline)\r
- ldisc->back->special(ldisc->backhandle, TS_EOL);\r
- else\r
- ldisc->back->send(ldisc->backhandle, "\r", 1);\r
- break;\r
- case CTRL('?'):\r
- case CTRL('H'):\r
- if (ldisc->cfg->telnet_keyboard) {\r
- ldisc->back->special(ldisc->backhandle, TS_EC);\r
- break;\r
- }\r
- case CTRL('C'):\r
- if (ldisc->cfg->telnet_keyboard) {\r
- ldisc->back->special(ldisc->backhandle, TS_IP);\r
- break;\r
- }\r
- case CTRL('Z'):\r
- if (ldisc->cfg->telnet_keyboard) {\r
- ldisc->back->special(ldisc->backhandle, TS_SUSP);\r
- break;\r
- }\r
-\r
- default:\r
- ldisc->back->send(ldisc->backhandle, buf, len);\r
- break;\r
- }\r
- } else\r
- ldisc->back->send(ldisc->backhandle, buf, len);\r
- }\r
- }\r
-}\r