0965bee0 |
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 | |
5bc238bb |
8 | #include <stdio.h> |
1d470ad2 |
9 | #include <ctype.h> |
5bc238bb |
10 | |
11 | #include "putty.h" |
887035a5 |
12 | #include "terminal.h" |
5def7522 |
13 | #include "ldisc.h" |
b9d7bcad |
14 | |
4a693cfc |
15 | #define ECHOING (ldisc->localecho == FORCE_ON || \ |
16 | (ldisc->localecho == AUTO && \ |
b9d7bcad |
17 | (ldisc->back->ldisc(ldisc->backhandle, LD_ECHO) || \ |
18 | term_ldisc(ldisc->term, LD_ECHO)))) |
4a693cfc |
19 | #define EDITING (ldisc->localedit == FORCE_ON || \ |
20 | (ldisc->localedit == AUTO && \ |
b9d7bcad |
21 | (ldisc->back->ldisc(ldisc->backhandle, LD_EDIT) || \ |
22 | term_ldisc(ldisc->term, LD_EDIT)))) |
5bc238bb |
23 | |
b9d7bcad |
24 | static void c_write(Ldisc ldisc, char *buf, int len) |
32874aea |
25 | { |
39d04c2b |
26 | from_backend(ldisc->frontend, 0, buf, len); |
fe50e814 |
27 | } |
28 | |
b9d7bcad |
29 | static int plen(Ldisc ldisc, unsigned char c) |
32874aea |
30 | { |
b9d7bcad |
31 | if ((c >= 32 && c <= 126) || (c >= 160 && !in_utf(ldisc->term))) |
32874aea |
32 | return 1; |
5bc238bb |
33 | else if (c < 128) |
32874aea |
34 | return 2; /* ^x for some x */ |
f0fccd51 |
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 */ |
5bc238bb |
40 | else |
f0fccd51 |
41 | return 4; /* <XY> hex representation */ |
5bc238bb |
42 | } |
43 | |
b9d7bcad |
44 | static void pwrite(Ldisc ldisc, unsigned char c) |
32874aea |
45 | { |
f0fccd51 |
46 | if ((c >= 32 && c <= 126) || |
47 | (!in_utf(ldisc->term) && c >= 0xA0) || |
48 | (in_utf(ldisc->term) && c >= 0x80)) { |
fe5634f6 |
49 | c_write(ldisc, (char *)&c, 1); |
5bc238bb |
50 | } else if (c < 128) { |
32874aea |
51 | char cc[2]; |
52 | cc[1] = (c == 127 ? '?' : c + 0x40); |
53 | cc[0] = '^'; |
b9d7bcad |
54 | c_write(ldisc, cc, 2); |
5bc238bb |
55 | } else { |
32874aea |
56 | char cc[5]; |
57 | sprintf(cc, "<%02X>", c); |
b9d7bcad |
58 | c_write(ldisc, cc, 4); |
5bc238bb |
59 | } |
60 | } |
61 | |
f0fccd51 |
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 | |
b9d7bcad |
70 | static void bsb(Ldisc ldisc, int n) |
32874aea |
71 | { |
5bc238bb |
72 | while (n--) |
b9d7bcad |
73 | c_write(ldisc, "\010 \010", 3); |
5bc238bb |
74 | } |
75 | |
5d1d4279 |
76 | #define CTRL(x) (x^'@') |
a5f3e637 |
77 | #define KCTRL(x) ((x^'@') | 0x100) |
5d1d4279 |
78 | |
4a693cfc |
79 | void *ldisc_create(Conf *conf, Terminal *term, |
b9d7bcad |
80 | Backend *back, void *backhandle, |
81 | void *frontend) |
32874aea |
82 | { |
3d88e64d |
83 | Ldisc ldisc = snew(struct ldisc_tag); |
b9d7bcad |
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 | |
4a693cfc |
95 | ldisc_configure(ldisc, conf); |
96 | |
b9d7bcad |
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 | |
4a693cfc |
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 | |
fabd1805 |
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 | |
b9d7bcad |
130 | void ldisc_send(void *handle, char *buf, int len, int interactive) |
131 | { |
132 | Ldisc ldisc = (Ldisc) handle; |
a5f3e637 |
133 | int keyflag = 0; |
0965bee0 |
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) { |
b9d7bcad |
139 | ldisc_update(ldisc->frontend, ECHOING, EDITING); |
887035a5 |
140 | return; |
5bc238bb |
141 | } |
0965bee0 |
142 | /* |
ab2c86b9 |
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 | */ |
b9d7bcad |
147 | frontend_keypress(ldisc->frontend); |
148 | |
ab2c86b9 |
149 | /* |
a5f3e637 |
150 | * Less than zero means null terminated special string. |
151 | */ |
152 | if (len < 0) { |
153 | len = strlen(buf); |
154 | keyflag = KCTRL('@'); |
155 | } |
156 | /* |
0965bee0 |
157 | * Either perform local editing, or just send characters. |
158 | */ |
159 | if (EDITING) { |
32874aea |
160 | while (len--) { |
a5f3e637 |
161 | int c; |
b96c714e |
162 | c = (unsigned char)(*buf++) + keyflag; |
760e88b2 |
163 | if (!interactive && c == '\r') |
164 | c += KCTRL('@'); |
b9d7bcad |
165 | switch (ldisc->quotenext ? ' ' : c) { |
32874aea |
166 | /* |
f0fccd51 |
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) |
32874aea |
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 | */ |
a5f3e637 |
182 | case KCTRL('H'): |
183 | case KCTRL('?'): /* backspace/delete */ |
b9d7bcad |
184 | if (ldisc->buflen > 0) { |
f0fccd51 |
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])); |
32874aea |
190 | } |
191 | break; |
192 | case CTRL('W'): /* delete word */ |
b9d7bcad |
193 | while (ldisc->buflen > 0) { |
32874aea |
194 | if (ECHOING) |
b9d7bcad |
195 | bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); |
196 | ldisc->buflen--; |
197 | if (ldisc->buflen > 0 && |
e93ed432 |
198 | isspace((unsigned char)ldisc->buf[ldisc->buflen-1]) && |
199 | !isspace((unsigned char)ldisc->buf[ldisc->buflen])) |
32874aea |
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 */ |
b9d7bcad |
207 | while (ldisc->buflen > 0) { |
32874aea |
208 | if (ECHOING) |
b9d7bcad |
209 | bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); |
210 | ldisc->buflen--; |
32874aea |
211 | } |
b9d7bcad |
212 | ldisc->back->special(ldisc->backhandle, TS_EL); |
2a8a449c |
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 | */ |
4a693cfc |
218 | if (!ldisc->telnet_keyboard) |
2a8a449c |
219 | goto default_case; |
32874aea |
220 | if (c == CTRL('C')) |
b9d7bcad |
221 | ldisc->back->special(ldisc->backhandle, TS_IP); |
32874aea |
222 | if (c == CTRL('Z')) |
b9d7bcad |
223 | ldisc->back->special(ldisc->backhandle, TS_SUSP); |
32874aea |
224 | if (c == CTRL('\\')) |
b9d7bcad |
225 | ldisc->back->special(ldisc->backhandle, TS_ABORT); |
32874aea |
226 | break; |
227 | case CTRL('R'): /* redraw line */ |
228 | if (ECHOING) { |
229 | int i; |
b9d7bcad |
230 | c_write(ldisc, "^R\r\n", 4); |
231 | for (i = 0; i < ldisc->buflen; i++) |
232 | pwrite(ldisc, ldisc->buf[i]); |
32874aea |
233 | } |
234 | break; |
235 | case CTRL('V'): /* quote next char */ |
b9d7bcad |
236 | ldisc->quotenext = TRUE; |
32874aea |
237 | break; |
238 | case CTRL('D'): /* logout or send */ |
b9d7bcad |
239 | if (ldisc->buflen == 0) { |
240 | ldisc->back->special(ldisc->backhandle, TS_EOF); |
32874aea |
241 | } else { |
b9d7bcad |
242 | ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); |
243 | ldisc->buflen = 0; |
32874aea |
244 | } |
245 | break; |
a5f3e637 |
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'): |
4a693cfc |
270 | if (ldisc->protocol == PROT_RAW && |
b9d7bcad |
271 | ldisc->buflen > 0 && ldisc->buf[ldisc->buflen - 1] == '\r') { |
a5f3e637 |
272 | if (ECHOING) |
b9d7bcad |
273 | bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); |
274 | ldisc->buflen--; |
a5f3e637 |
275 | /* FALLTHROUGH */ |
276 | case KCTRL('M'): /* send with newline */ |
b9d7bcad |
277 | if (ldisc->buflen > 0) |
278 | ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); |
4a693cfc |
279 | if (ldisc->protocol == PROT_RAW) |
b9d7bcad |
280 | ldisc->back->send(ldisc->backhandle, "\r\n", 2); |
4a693cfc |
281 | else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline) |
b9d7bcad |
282 | ldisc->back->special(ldisc->backhandle, TS_EOL); |
a5f3e637 |
283 | else |
b9d7bcad |
284 | ldisc->back->send(ldisc->backhandle, "\r", 1); |
a5f3e637 |
285 | if (ECHOING) |
b9d7bcad |
286 | c_write(ldisc, "\r\n", 2); |
287 | ldisc->buflen = 0; |
a5f3e637 |
288 | break; |
289 | } |
290 | /* FALLTHROUGH */ |
32874aea |
291 | default: /* get to this label from ^V handler */ |
2a8a449c |
292 | default_case: |
b9d7bcad |
293 | if (ldisc->buflen >= ldisc->bufsiz) { |
294 | ldisc->bufsiz = ldisc->buflen + 256; |
3d88e64d |
295 | ldisc->buf = sresize(ldisc->buf, ldisc->bufsiz, char); |
32874aea |
296 | } |
b9d7bcad |
297 | ldisc->buf[ldisc->buflen++] = c; |
32874aea |
298 | if (ECHOING) |
b9d7bcad |
299 | pwrite(ldisc, (unsigned char) c); |
300 | ldisc->quotenext = FALSE; |
32874aea |
301 | break; |
302 | } |
303 | } |
0965bee0 |
304 | } else { |
b9d7bcad |
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--; |
32874aea |
310 | } |
311 | } |
312 | if (len > 0) { |
313 | if (ECHOING) |
b9d7bcad |
314 | c_write(ldisc, buf, len); |
4a693cfc |
315 | if (keyflag && ldisc->protocol == PROT_TELNET && len == 1) { |
a5f3e637 |
316 | switch (buf[0]) { |
317 | case CTRL('M'): |
4a693cfc |
318 | if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline) |
b9d7bcad |
319 | ldisc->back->special(ldisc->backhandle, TS_EOL); |
eee63b77 |
320 | else |
b9d7bcad |
321 | ldisc->back->send(ldisc->backhandle, "\r", 1); |
a5f3e637 |
322 | break; |
323 | case CTRL('?'): |
324 | case CTRL('H'): |
4a693cfc |
325 | if (ldisc->telnet_keyboard) { |
b9d7bcad |
326 | ldisc->back->special(ldisc->backhandle, TS_EC); |
a5f3e637 |
327 | break; |
328 | } |
329 | case CTRL('C'): |
4a693cfc |
330 | if (ldisc->telnet_keyboard) { |
b9d7bcad |
331 | ldisc->back->special(ldisc->backhandle, TS_IP); |
a5f3e637 |
332 | break; |
333 | } |
334 | case CTRL('Z'): |
4a693cfc |
335 | if (ldisc->telnet_keyboard) { |
b9d7bcad |
336 | ldisc->back->special(ldisc->backhandle, TS_SUSP); |
a5f3e637 |
337 | break; |
338 | } |
339 | |
340 | default: |
b9d7bcad |
341 | ldisc->back->send(ldisc->backhandle, buf, len); |
a5f3e637 |
342 | break; |
343 | } |
344 | } else |
b9d7bcad |
345 | ldisc->back->send(ldisc->backhandle, buf, len); |
32874aea |
346 | } |
5d1d4279 |
347 | } |
5bc238bb |
348 | } |