]>
Commit | Line | Data |
---|---|---|
1c1af145 | 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->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(Config *mycfg, 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->cfg = mycfg; | |
91 | ldisc->back = back; | |
92 | ldisc->backhandle = backhandle; | |
93 | ldisc->term = term; | |
94 | ldisc->frontend = frontend; | |
95 | ||
96 | /* Link ourselves into the backend and the terminal */ | |
97 | if (term) | |
98 | term->ldisc = ldisc; | |
99 | if (back) | |
100 | back->provide_ldisc(backhandle, ldisc); | |
101 | ||
102 | return ldisc; | |
103 | } | |
104 | ||
105 | void ldisc_free(void *handle) | |
106 | { | |
107 | Ldisc ldisc = (Ldisc) handle; | |
108 | ||
109 | if (ldisc->term) | |
110 | ldisc->term->ldisc = NULL; | |
111 | if (ldisc->back) | |
112 | ldisc->back->provide_ldisc(ldisc->backhandle, NULL); | |
113 | if (ldisc->buf) | |
114 | sfree(ldisc->buf); | |
115 | sfree(ldisc); | |
116 | } | |
117 | ||
118 | void ldisc_send(void *handle, char *buf, int len, int interactive) | |
119 | { | |
120 | Ldisc ldisc = (Ldisc) handle; | |
121 | int keyflag = 0; | |
122 | /* | |
123 | * Called with len=0 when the options change. We must inform | |
124 | * the front end in case it needs to know. | |
125 | */ | |
126 | if (len == 0) { | |
127 | ldisc_update(ldisc->frontend, ECHOING, EDITING); | |
128 | return; | |
129 | } | |
130 | /* | |
131 | * Notify the front end that something was pressed, in case | |
132 | * it's depending on finding out (e.g. keypress termination for | |
133 | * Close On Exit). | |
134 | */ | |
135 | frontend_keypress(ldisc->frontend); | |
136 | ||
137 | /* | |
138 | * Less than zero means null terminated special string. | |
139 | */ | |
140 | if (len < 0) { | |
141 | len = strlen(buf); | |
142 | keyflag = KCTRL('@'); | |
143 | } | |
144 | /* | |
145 | * Either perform local editing, or just send characters. | |
146 | */ | |
147 | if (EDITING) { | |
148 | while (len--) { | |
149 | int c; | |
150 | c = (unsigned char)(*buf++) + keyflag; | |
151 | if (!interactive && c == '\r') | |
152 | c += KCTRL('@'); | |
153 | switch (ldisc->quotenext ? ' ' : c) { | |
154 | /* | |
155 | * ^h/^?: delete, and output BSBs, to return to | |
156 | * last character boundary (in UTF-8 mode this may | |
157 | * be more than one byte) | |
158 | * ^w: delete, and output BSBs, to return to last | |
159 | * space/nonspace boundary | |
160 | * ^u: delete, and output BSBs, to return to BOL | |
161 | * ^c: Do a ^u then send a telnet IP | |
162 | * ^z: Do a ^u then send a telnet SUSP | |
163 | * ^\: Do a ^u then send a telnet ABORT | |
164 | * ^r: echo "^R\n" and redraw line | |
165 | * ^v: quote next char | |
166 | * ^d: if at BOL, end of file and close connection, | |
167 | * else send line and reset to BOL | |
168 | * ^m: send line-plus-\r\n and reset to BOL | |
169 | */ | |
170 | case KCTRL('H'): | |
171 | case KCTRL('?'): /* backspace/delete */ | |
172 | if (ldisc->buflen > 0) { | |
173 | do { | |
174 | if (ECHOING) | |
175 | bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); | |
176 | ldisc->buflen--; | |
177 | } while (!char_start(ldisc, ldisc->buf[ldisc->buflen])); | |
178 | } | |
179 | break; | |
180 | case CTRL('W'): /* delete word */ | |
181 | while (ldisc->buflen > 0) { | |
182 | if (ECHOING) | |
183 | bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); | |
184 | ldisc->buflen--; | |
185 | if (ldisc->buflen > 0 && | |
186 | isspace((unsigned char)ldisc->buf[ldisc->buflen-1]) && | |
187 | !isspace((unsigned char)ldisc->buf[ldisc->buflen])) | |
188 | break; | |
189 | } | |
190 | break; | |
191 | case CTRL('U'): /* delete line */ | |
192 | case CTRL('C'): /* Send IP */ | |
193 | case CTRL('\\'): /* Quit */ | |
194 | case CTRL('Z'): /* Suspend */ | |
195 | while (ldisc->buflen > 0) { | |
196 | if (ECHOING) | |
197 | bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); | |
198 | ldisc->buflen--; | |
199 | } | |
200 | ldisc->back->special(ldisc->backhandle, TS_EL); | |
201 | /* | |
202 | * We don't send IP, SUSP or ABORT if the user has | |
203 | * configured telnet specials off! This breaks | |
204 | * talkers otherwise. | |
205 | */ | |
206 | if (!ldisc->cfg->telnet_keyboard) | |
207 | goto default_case; | |
208 | if (c == CTRL('C')) | |
209 | ldisc->back->special(ldisc->backhandle, TS_IP); | |
210 | if (c == CTRL('Z')) | |
211 | ldisc->back->special(ldisc->backhandle, TS_SUSP); | |
212 | if (c == CTRL('\\')) | |
213 | ldisc->back->special(ldisc->backhandle, TS_ABORT); | |
214 | break; | |
215 | case CTRL('R'): /* redraw line */ | |
216 | if (ECHOING) { | |
217 | int i; | |
218 | c_write(ldisc, "^R\r\n", 4); | |
219 | for (i = 0; i < ldisc->buflen; i++) | |
220 | pwrite(ldisc, ldisc->buf[i]); | |
221 | } | |
222 | break; | |
223 | case CTRL('V'): /* quote next char */ | |
224 | ldisc->quotenext = TRUE; | |
225 | break; | |
226 | case CTRL('D'): /* logout or send */ | |
227 | if (ldisc->buflen == 0) { | |
228 | ldisc->back->special(ldisc->backhandle, TS_EOF); | |
229 | } else { | |
230 | ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); | |
231 | ldisc->buflen = 0; | |
232 | } | |
233 | break; | |
234 | /* | |
235 | * This particularly hideous bit of code from RDB | |
236 | * allows ordinary ^M^J to do the same thing as | |
237 | * magic-^M when in Raw protocol. The line `case | |
238 | * KCTRL('M'):' is _inside_ the if block. Thus: | |
239 | * | |
240 | * - receiving regular ^M goes straight to the | |
241 | * default clause and inserts as a literal ^M. | |
242 | * - receiving regular ^J _not_ directly after a | |
243 | * literal ^M (or not in Raw protocol) fails the | |
244 | * if condition, leaps to the bottom of the if, | |
245 | * and falls through into the default clause | |
246 | * again. | |
247 | * - receiving regular ^J just after a literal ^M | |
248 | * in Raw protocol passes the if condition, | |
249 | * deletes the literal ^M, and falls through | |
250 | * into the magic-^M code | |
251 | * - receiving a magic-^M empties the line buffer, | |
252 | * signals end-of-line in one of the various | |
253 | * entertaining ways, and _doesn't_ fall out of | |
254 | * the bottom of the if and through to the | |
255 | * default clause because of the break. | |
256 | */ | |
257 | case CTRL('J'): | |
258 | if (ldisc->cfg->protocol == PROT_RAW && | |
259 | ldisc->buflen > 0 && ldisc->buf[ldisc->buflen - 1] == '\r') { | |
260 | if (ECHOING) | |
261 | bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); | |
262 | ldisc->buflen--; | |
263 | /* FALLTHROUGH */ | |
264 | case KCTRL('M'): /* send with newline */ | |
265 | if (ldisc->buflen > 0) | |
266 | ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); | |
267 | if (ldisc->cfg->protocol == PROT_RAW) | |
268 | ldisc->back->send(ldisc->backhandle, "\r\n", 2); | |
269 | else if (ldisc->cfg->protocol == PROT_TELNET && ldisc->cfg->telnet_newline) | |
270 | ldisc->back->special(ldisc->backhandle, TS_EOL); | |
271 | else | |
272 | ldisc->back->send(ldisc->backhandle, "\r", 1); | |
273 | if (ECHOING) | |
274 | c_write(ldisc, "\r\n", 2); | |
275 | ldisc->buflen = 0; | |
276 | break; | |
277 | } | |
278 | /* FALLTHROUGH */ | |
279 | default: /* get to this label from ^V handler */ | |
280 | default_case: | |
281 | if (ldisc->buflen >= ldisc->bufsiz) { | |
282 | ldisc->bufsiz = ldisc->buflen + 256; | |
283 | ldisc->buf = sresize(ldisc->buf, ldisc->bufsiz, char); | |
284 | } | |
285 | ldisc->buf[ldisc->buflen++] = c; | |
286 | if (ECHOING) | |
287 | pwrite(ldisc, (unsigned char) c); | |
288 | ldisc->quotenext = FALSE; | |
289 | break; | |
290 | } | |
291 | } | |
292 | } else { | |
293 | if (ldisc->buflen != 0) { | |
294 | ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); | |
295 | while (ldisc->buflen > 0) { | |
296 | bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); | |
297 | ldisc->buflen--; | |
298 | } | |
299 | } | |
300 | if (len > 0) { | |
301 | if (ECHOING) | |
302 | c_write(ldisc, buf, len); | |
303 | if (keyflag && ldisc->cfg->protocol == PROT_TELNET && len == 1) { | |
304 | switch (buf[0]) { | |
305 | case CTRL('M'): | |
306 | if (ldisc->cfg->protocol == PROT_TELNET && ldisc->cfg->telnet_newline) | |
307 | ldisc->back->special(ldisc->backhandle, TS_EOL); | |
308 | else | |
309 | ldisc->back->send(ldisc->backhandle, "\r", 1); | |
310 | break; | |
311 | case CTRL('?'): | |
312 | case CTRL('H'): | |
313 | if (ldisc->cfg->telnet_keyboard) { | |
314 | ldisc->back->special(ldisc->backhandle, TS_EC); | |
315 | break; | |
316 | } | |
317 | case CTRL('C'): | |
318 | if (ldisc->cfg->telnet_keyboard) { | |
319 | ldisc->back->special(ldisc->backhandle, TS_IP); | |
320 | break; | |
321 | } | |
322 | case CTRL('Z'): | |
323 | if (ldisc->cfg->telnet_keyboard) { | |
324 | ldisc->back->special(ldisc->backhandle, TS_SUSP); | |
325 | break; | |
326 | } | |
327 | ||
328 | default: | |
329 | ldisc->back->send(ldisc->backhandle, buf, len); | |
330 | break; | |
331 | } | |
332 | } else | |
333 | ldisc->back->send(ldisc->backhandle, buf, len); | |
334 | } | |
335 | } | |
336 | } |