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