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