Update libgit2 AutoCRLF patches
[TortoiseGit.git] / src / TortoisePlink / LDISC.C
blobf95b2fdc66bac2ea062872b1726bc86e7e8873fc
1 /*\r
2  * ldisc.c: PuTTY line discipline. Sits between the input coming\r
3  * from keypresses in the window, and the output channel leading to\r
4  * the back end. Implements echo and/or local line editing,\r
5  * depending on what's currently configured.\r
6  */\r
7 \r
8 #include <stdio.h>\r
9 #include <ctype.h>\r
10 #include <assert.h>\r
12 #include "putty.h"\r
13 #include "terminal.h"\r
14 #include "ldisc.h"\r
16 #define ECHOING (ldisc->localecho == FORCE_ON || \\r
17                  (ldisc->localecho == AUTO && \\r
18                       (ldisc->back->ldisc(ldisc->backhandle, LD_ECHO) || \\r
19                            term_ldisc(ldisc->term, LD_ECHO))))\r
20 #define EDITING (ldisc->localedit == FORCE_ON || \\r
21                  (ldisc->localedit == AUTO && \\r
22                       (ldisc->back->ldisc(ldisc->backhandle, LD_EDIT) || \\r
23                            term_ldisc(ldisc->term, LD_EDIT))))\r
25 static void c_write(Ldisc ldisc, char *buf, int len)\r
26 {\r
27     from_backend(ldisc->frontend, 0, buf, len);\r
28 }\r
30 static int plen(Ldisc ldisc, unsigned char c)\r
31 {\r
32     if ((c >= 32 && c <= 126) || (c >= 160 && !in_utf(ldisc->term)))\r
33         return 1;\r
34     else if (c < 128)\r
35         return 2;                      /* ^x for some x */\r
36     else if (in_utf(ldisc->term) && c >= 0xC0)\r
37         return 1;                      /* UTF-8 introducer character\r
38                                         * (FIXME: combining / wide chars) */\r
39     else if (in_utf(ldisc->term) && c >= 0x80 && c < 0xC0)\r
40         return 0;                      /* UTF-8 followup character */\r
41     else\r
42         return 4;                      /* <XY> hex representation */\r
43 }\r
45 static void pwrite(Ldisc ldisc, unsigned char c)\r
46 {\r
47     if ((c >= 32 && c <= 126) ||\r
48         (!in_utf(ldisc->term) && c >= 0xA0) ||\r
49         (in_utf(ldisc->term) && c >= 0x80)) {\r
50         c_write(ldisc, (char *)&c, 1);\r
51     } else if (c < 128) {\r
52         char cc[2];\r
53         cc[1] = (c == 127 ? '?' : c + 0x40);\r
54         cc[0] = '^';\r
55         c_write(ldisc, cc, 2);\r
56     } else {\r
57         char cc[5];\r
58         sprintf(cc, "<%02X>", c);\r
59         c_write(ldisc, cc, 4);\r
60     }\r
61 }\r
63 static int char_start(Ldisc ldisc, unsigned char c)\r
64 {\r
65     if (in_utf(ldisc->term))\r
66         return (c < 0x80 || c >= 0xC0);\r
67     else\r
68         return 1;\r
69 }\r
71 static void bsb(Ldisc ldisc, int n)\r
72 {\r
73     while (n--)\r
74         c_write(ldisc, "\010 \010", 3);\r
75 }\r
77 #define CTRL(x) (x^'@')\r
78 #define KCTRL(x) ((x^'@') | 0x100)\r
80 void *ldisc_create(Conf *conf, Terminal *term,\r
81                    Backend *back, void *backhandle,\r
82                    void *frontend)\r
83 {\r
84     Ldisc ldisc = snew(struct ldisc_tag);\r
86     ldisc->buf = NULL;\r
87     ldisc->buflen = 0;\r
88     ldisc->bufsiz = 0;\r
89     ldisc->quotenext = 0;\r
91     ldisc->back = back;\r
92     ldisc->backhandle = backhandle;\r
93     ldisc->term = term;\r
94     ldisc->frontend = frontend;\r
96     ldisc_configure(ldisc, conf);\r
98     /* Link ourselves into the backend and the terminal */\r
99     if (term)\r
100         term->ldisc = ldisc;\r
101     if (back)\r
102         back->provide_ldisc(backhandle, ldisc);\r
104     return ldisc;\r
107 void ldisc_configure(void *handle, Conf *conf)\r
109     Ldisc ldisc = (Ldisc) handle;\r
111     ldisc->telnet_keyboard = conf_get_int(conf, CONF_telnet_keyboard);\r
112     ldisc->telnet_newline = conf_get_int(conf, CONF_telnet_newline);\r
113     ldisc->protocol = conf_get_int(conf, CONF_protocol);\r
114     ldisc->localecho = conf_get_int(conf, CONF_localecho);\r
115     ldisc->localedit = conf_get_int(conf, CONF_localedit);\r
118 void ldisc_free(void *handle)\r
120     Ldisc ldisc = (Ldisc) handle;\r
122     if (ldisc->term)\r
123         ldisc->term->ldisc = NULL;\r
124     if (ldisc->back)\r
125         ldisc->back->provide_ldisc(ldisc->backhandle, NULL);\r
126     if (ldisc->buf)\r
127         sfree(ldisc->buf);\r
128     sfree(ldisc);\r
131 void ldisc_send(void *handle, char *buf, int len, int interactive)\r
133     Ldisc ldisc = (Ldisc) handle;\r
134     int keyflag = 0;\r
135     /*\r
136      * Called with len=0 when the options change. We must inform\r
137      * the front end in case it needs to know.\r
138      */\r
139     if (len == 0) {\r
140         ldisc_update(ldisc->frontend, ECHOING, EDITING);\r
141         return;\r
142     }\r
144     /*\r
145      * If that wasn't true, then we expect ldisc->term to be non-NULL\r
146      * hereafter. (The only front ends which have an ldisc but no term\r
147      * are those which do networking but no terminal emulation, in\r
148      * which case they need the above if statement to handle\r
149      * ldisc_updates passed from the back ends, but should never send\r
150      * any actual input through this function.)\r
151      */\r
152     assert(ldisc->term);\r
154     /*\r
155      * Notify the front end that something was pressed, in case\r
156      * it's depending on finding out (e.g. keypress termination for\r
157      * Close On Exit). \r
158      */\r
159     frontend_keypress(ldisc->frontend);\r
161     if (interactive) {\r
162         /*\r
163          * Interrupt a paste from the clipboard, if one was in\r
164          * progress when the user pressed a key. This is easier than\r
165          * buffering the current piece of data and saving it until the\r
166          * terminal has finished pasting, and has the potential side\r
167          * benefit of permitting a user to cancel an accidental huge\r
168          * paste.\r
169          */\r
170         term_nopaste(ldisc->term);\r
171     }\r
173     /*\r
174      * Less than zero means null terminated special string.\r
175      */\r
176     if (len < 0) {\r
177         len = strlen(buf);\r
178         keyflag = KCTRL('@');\r
179     }\r
180     /*\r
181      * Either perform local editing, or just send characters.\r
182      */\r
183     if (EDITING) {\r
184         while (len--) {\r
185             int c;\r
186             c = (unsigned char)(*buf++) + keyflag;\r
187             if (!interactive && c == '\r')\r
188                 c += KCTRL('@');\r
189             switch (ldisc->quotenext ? ' ' : c) {\r
190                 /*\r
191                  * ^h/^?: delete, and output BSBs, to return to\r
192                  * last character boundary (in UTF-8 mode this may\r
193                  * be more than one byte)\r
194                  * ^w: delete, and output BSBs, to return to last\r
195                  * space/nonspace boundary\r
196                  * ^u: delete, and output BSBs, to return to BOL\r
197                  * ^c: Do a ^u then send a telnet IP\r
198                  * ^z: Do a ^u then send a telnet SUSP\r
199                  * ^\: Do a ^u then send a telnet ABORT\r
200                  * ^r: echo "^R\n" and redraw line\r
201                  * ^v: quote next char\r
202                  * ^d: if at BOL, end of file and close connection,\r
203                  * else send line and reset to BOL\r
204                  * ^m: send line-plus-\r\n and reset to BOL\r
205                  */\r
206               case KCTRL('H'):\r
207               case KCTRL('?'):         /* backspace/delete */\r
208                 if (ldisc->buflen > 0) {\r
209                     do {\r
210                         if (ECHOING)\r
211                             bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));\r
212                         ldisc->buflen--;\r
213                     } while (!char_start(ldisc, ldisc->buf[ldisc->buflen]));\r
214                 }\r
215                 break;\r
216               case CTRL('W'):          /* delete word */\r
217                 while (ldisc->buflen > 0) {\r
218                     if (ECHOING)\r
219                         bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));\r
220                     ldisc->buflen--;\r
221                     if (ldisc->buflen > 0 &&\r
222                         isspace((unsigned char)ldisc->buf[ldisc->buflen-1]) &&\r
223                         !isspace((unsigned char)ldisc->buf[ldisc->buflen]))\r
224                         break;\r
225                 }\r
226                 break;\r
227               case CTRL('U'):          /* delete line */\r
228               case CTRL('C'):          /* Send IP */\r
229               case CTRL('\\'):         /* Quit */\r
230               case CTRL('Z'):          /* Suspend */\r
231                 while (ldisc->buflen > 0) {\r
232                     if (ECHOING)\r
233                         bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));\r
234                     ldisc->buflen--;\r
235                 }\r
236                 ldisc->back->special(ldisc->backhandle, TS_EL);\r
237                 /*\r
238                  * We don't send IP, SUSP or ABORT if the user has\r
239                  * configured telnet specials off! This breaks\r
240                  * talkers otherwise.\r
241                  */\r
242                 if (!ldisc->telnet_keyboard)\r
243                     goto default_case;\r
244                 if (c == CTRL('C'))\r
245                     ldisc->back->special(ldisc->backhandle, TS_IP);\r
246                 if (c == CTRL('Z'))\r
247                     ldisc->back->special(ldisc->backhandle, TS_SUSP);\r
248                 if (c == CTRL('\\'))\r
249                     ldisc->back->special(ldisc->backhandle, TS_ABORT);\r
250                 break;\r
251               case CTRL('R'):          /* redraw line */\r
252                 if (ECHOING) {\r
253                     int i;\r
254                     c_write(ldisc, "^R\r\n", 4);\r
255                     for (i = 0; i < ldisc->buflen; i++)\r
256                         pwrite(ldisc, ldisc->buf[i]);\r
257                 }\r
258                 break;\r
259               case CTRL('V'):          /* quote next char */\r
260                 ldisc->quotenext = TRUE;\r
261                 break;\r
262               case CTRL('D'):          /* logout or send */\r
263                 if (ldisc->buflen == 0) {\r
264                     ldisc->back->special(ldisc->backhandle, TS_EOF);\r
265                 } else {\r
266                     ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);\r
267                     ldisc->buflen = 0;\r
268                 }\r
269                 break;\r
270                 /*\r
271                  * This particularly hideous bit of code from RDB\r
272                  * allows ordinary ^M^J to do the same thing as\r
273                  * magic-^M when in Raw protocol. The line `case\r
274                  * KCTRL('M'):' is _inside_ the if block. Thus:\r
275                  * \r
276                  *  - receiving regular ^M goes straight to the\r
277                  *    default clause and inserts as a literal ^M.\r
278                  *  - receiving regular ^J _not_ directly after a\r
279                  *    literal ^M (or not in Raw protocol) fails the\r
280                  *    if condition, leaps to the bottom of the if,\r
281                  *    and falls through into the default clause\r
282                  *    again.\r
283                  *  - receiving regular ^J just after a literal ^M\r
284                  *    in Raw protocol passes the if condition,\r
285                  *    deletes the literal ^M, and falls through\r
286                  *    into the magic-^M code\r
287                  *  - receiving a magic-^M empties the line buffer,\r
288                  *    signals end-of-line in one of the various\r
289                  *    entertaining ways, and _doesn't_ fall out of\r
290                  *    the bottom of the if and through to the\r
291                  *    default clause because of the break.\r
292                  */\r
293               case CTRL('J'):\r
294                 if (ldisc->protocol == PROT_RAW &&\r
295                     ldisc->buflen > 0 && ldisc->buf[ldisc->buflen - 1] == '\r') {\r
296                     if (ECHOING)\r
297                         bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));\r
298                     ldisc->buflen--;\r
299                     /* FALLTHROUGH */\r
300               case KCTRL('M'):         /* send with newline */\r
301                     if (ldisc->buflen > 0)\r
302                         ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);\r
303                     if (ldisc->protocol == PROT_RAW)\r
304                         ldisc->back->send(ldisc->backhandle, "\r\n", 2);\r
305                     else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline)\r
306                         ldisc->back->special(ldisc->backhandle, TS_EOL);\r
307                     else\r
308                         ldisc->back->send(ldisc->backhandle, "\r", 1);\r
309                     if (ECHOING)\r
310                         c_write(ldisc, "\r\n", 2);\r
311                     ldisc->buflen = 0;\r
312                     break;\r
313                 }\r
314                 /* FALLTHROUGH */\r
315               default:                 /* get to this label from ^V handler */\r
316                 default_case:\r
317                 if (ldisc->buflen >= ldisc->bufsiz) {\r
318                     ldisc->bufsiz = ldisc->buflen + 256;\r
319                     ldisc->buf = sresize(ldisc->buf, ldisc->bufsiz, char);\r
320                 }\r
321                 ldisc->buf[ldisc->buflen++] = c;\r
322                 if (ECHOING)\r
323                     pwrite(ldisc, (unsigned char) c);\r
324                 ldisc->quotenext = FALSE;\r
325                 break;\r
326             }\r
327         }\r
328     } else {\r
329         if (ldisc->buflen != 0) {\r
330             ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);\r
331             while (ldisc->buflen > 0) {\r
332                 bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));\r
333                 ldisc->buflen--;\r
334             }\r
335         }\r
336         if (len > 0) {\r
337             if (ECHOING)\r
338                 c_write(ldisc, buf, len);\r
339             if (keyflag && ldisc->protocol == PROT_TELNET && len == 1) {\r
340                 switch (buf[0]) {\r
341                   case CTRL('M'):\r
342                     if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline)\r
343                         ldisc->back->special(ldisc->backhandle, TS_EOL);\r
344                     else\r
345                         ldisc->back->send(ldisc->backhandle, "\r", 1);\r
346                     break;\r
347                   case CTRL('?'):\r
348                   case CTRL('H'):\r
349                     if (ldisc->telnet_keyboard) {\r
350                         ldisc->back->special(ldisc->backhandle, TS_EC);\r
351                         break;\r
352                     }\r
353                   case CTRL('C'):\r
354                     if (ldisc->telnet_keyboard) {\r
355                         ldisc->back->special(ldisc->backhandle, TS_IP);\r
356                         break;\r
357                     }\r
358                   case CTRL('Z'):\r
359                     if (ldisc->telnet_keyboard) {\r
360                         ldisc->back->special(ldisc->backhandle, TS_SUSP);\r
361                         break;\r
362                     }\r
364                   default:\r
365                     ldisc->back->send(ldisc->backhandle, buf, len);\r
366                     break;\r
367                 }\r
368             } else\r
369                 ldisc->back->send(ldisc->backhandle, buf, len);\r
370         }\r
371     }\r