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