terminal: Escape sequence handling fixes

Upgrade and refactor terminal_data to properly handle non-csi escape
codes, control characters in escape codes, and invalid escape sequences.
Also fix a buffer overflow in the escape sequence buffer.

Signed-off-by: Callum Lowcay <callum@callumscode.com>
dev
Callum Lowcay 14 years ago committed by Kristian Høgsberg
parent a0ee21c7dc
commit b8609ada50
  1. 201
      clients/terminal.c

@ -52,6 +52,10 @@ static int option_fullscreen;
#define ATTRMASK_BLINK 0x04 #define ATTRMASK_BLINK 0x04
#define ATTRMASK_INVERSE 0x08 #define ATTRMASK_INVERSE 0x08
/* Buffer sizes */
#define MAX_RESPONSE 11
#define MAX_ESCAPE 64
union utf8_char { union utf8_char {
unsigned char byte[4]; unsigned char byte[4];
uint32_t ch; uint32_t ch;
@ -189,9 +193,10 @@ struct terminal {
int fd, master; int fd, master;
GIOChannel *channel; GIOChannel *channel;
uint32_t modifiers; uint32_t modifiers;
char escape[64]; char escape[MAX_ESCAPE];
int escape_length; int escape_length;
int state; int state;
int qmark_flag;
struct utf8_state_machine state_machine; struct utf8_state_machine state_machine;
int margin; int margin;
int fullscreen; int fullscreen;
@ -513,10 +518,15 @@ redraw_handler(struct window *window, void *data)
#define STATE_NORMAL 0 #define STATE_NORMAL 0
#define STATE_ESCAPE 1 #define STATE_ESCAPE 1
#define STATE_ESCAPE_SPECIAL 2
#define STATE_ESCAPE_CSI 3
static void static void
terminal_data(struct terminal *terminal, const char *data, size_t length); terminal_data(struct terminal *terminal, const char *data, size_t length);
static void
handle_char(struct terminal *terminal, union utf8_char utf8);
static void static void
handle_sgr(struct terminal *terminal, int code); handle_sgr(struct terminal *terminal, int code);
@ -629,13 +639,21 @@ handle_escape(struct terminal *terminal)
} }
break; break;
default: default:
terminal_data(terminal, fprintf(stderr, "Unknown CSI escape: %c\n", *p);
terminal->escape + 1,
terminal->escape_length - 2);
break; break;
} }
} }
static void
handle_non_csi_escape(struct terminal *terminal, char code)
{
}
static void
handle_special_escape(struct terminal *terminal, char special, char code)
{
}
static void static void
handle_sgr(struct terminal *terminal, int code) handle_sgr(struct terminal *terminal, int code)
{ {
@ -698,58 +716,25 @@ handle_sgr(struct terminal *terminal, int code)
} }
} }
static void /* Returns 1 if c was special, otherwise 0 */
terminal_data(struct terminal *terminal, const char *data, size_t length) static int
handle_special_char(struct terminal *terminal, char c)
{ {
int i;
union utf8_char utf8;
enum utf8_state parser_state;
union utf8_char *row; union utf8_char *row;
struct attr *attr_row; struct attr *attr_row;
for (i = 0; i < length; i++) {
parser_state =
utf8_next_char(&terminal->state_machine, data[i]);
switch(parser_state) {
case utf8state_accept:
utf8.ch = terminal->state_machine.s.ch;
break;
case utf8state_reject:
/* the unicode replacement character */
utf8.byte[0] = 0xEF;
utf8.byte[1] = 0xBF;
utf8.byte[2] = 0xBD;
utf8.byte[3] = 0x00;
break;
default:
continue;
}
row = terminal_get_row(terminal, terminal->row); row = terminal_get_row(terminal, terminal->row);
attr_row = terminal_get_attr_row(terminal, terminal->row); attr_row = terminal_get_attr_row(terminal, terminal->row);
if (terminal->state == STATE_ESCAPE) { switch(c) {
terminal->escape[terminal->escape_length++] = utf8.byte[0];
if (terminal->escape_length == 2 && utf8.byte[0] != '[') {
/* Bad escape sequence. */
terminal->state = STATE_NORMAL;
goto cancel_escape;
}
if (isalpha(utf8.byte[0])) {
terminal->state = STATE_NORMAL;
handle_escape(terminal);
}
continue;
}
cancel_escape:
switch (utf8.byte[0]) {
case '\r': case '\r':
terminal->column = 0; terminal->column = 0;
break; break;
case '\n': case '\n':
terminal->column = 0; terminal->column = 0;
/* fallthrough */
case '\v':
case '\f':
if (terminal->row + 1 < terminal->height) { if (terminal->row + 1 < terminal->height) {
terminal->row++; terminal->row++;
} else { } else {
@ -768,11 +753,6 @@ terminal_data(struct terminal *terminal, const char *data, size_t length)
attr_init(&attr_row[terminal->column], terminal->curr_attr, -terminal->column & 7); attr_init(&attr_row[terminal->column], terminal->curr_attr, -terminal->column & 7);
terminal->column = (terminal->column + 7) & ~7; terminal->column = (terminal->column + 7) & ~7;
break; break;
case '\e':
terminal->state = STATE_ESCAPE;
terminal->escape[0] = '\e';
terminal->escape_length = 1;
break;
case '\b': case '\b':
if (terminal->column > 0) if (terminal->column > 0)
terminal->column--; terminal->column--;
@ -781,14 +761,133 @@ terminal_data(struct terminal *terminal, const char *data, size_t length)
/* Bell */ /* Bell */
break; break;
default: default:
if (terminal->column < terminal->width) return 0;
if (utf8.byte[0] < 32) utf8.byte[0] += 64; }
return 1;
}
static void
handle_char(struct terminal *terminal, union utf8_char utf8)
{
union utf8_char *row;
struct attr *attr_row;
if (handle_special_char(terminal, utf8.byte[0])) return;
/* There are a whole lot of non-characters, control codes,
* and formatting codes that should probably be ignored,
* for example: */
if (strncmp((char*) utf8.byte, "\xEF\xBB\xBF", 3) == 0) {
/* BOM, ignore */
return;
}
/* Some of these non-characters should be translated, e.g.: */
if (utf8.byte[0] < 32) {
utf8.byte[0] = utf8.byte[0] + 64;
}
/* handle right margin effects */
if (terminal->column >= terminal->width) {
terminal->column--;
}
row = terminal_get_row(terminal, terminal->row);
attr_row = terminal_get_attr_row(terminal, terminal->row);
row[terminal->column] = utf8; row[terminal->column] = utf8;
attr_row[terminal->column++] = terminal->curr_attr; attr_row[terminal->column++] = terminal->curr_attr;
if (utf8.ch != terminal->last_char.ch)
terminal->last_char = utf8;
}
static void
terminal_data(struct terminal *terminal, const char *data, size_t length)
{
int i;
union utf8_char utf8;
enum utf8_state parser_state;
for (i = 0; i < length; i++) {
parser_state =
utf8_next_char(&terminal->state_machine, data[i]);
switch(parser_state) {
case utf8state_accept:
utf8.ch = terminal->state_machine.s.ch;
break; break;
case utf8state_reject:
/* the unicode replacement character */
utf8.byte[0] = 0xEF;
utf8.byte[1] = 0xBF;
utf8.byte[2] = 0xBD;
utf8.byte[3] = 0x00;
break;
default:
continue;
}
/* assume escape codes never use non-ASCII characters */
if (terminal->state == STATE_ESCAPE) {
terminal->escape[terminal->escape_length++] = utf8.byte[0];
if (utf8.byte[0] == '[') {
terminal->state = STATE_ESCAPE_CSI;
continue;
} else if (utf8.byte[0] == '#' || utf8.byte[0] == '(' ||
utf8.byte[0] == ')')
{
terminal->state = STATE_ESCAPE_SPECIAL;
continue;
} else {
terminal->state = STATE_NORMAL;
handle_non_csi_escape(terminal, utf8.byte[0]);
continue;
}
} else if (terminal->state == STATE_ESCAPE_SPECIAL) {
terminal->escape[terminal->escape_length++] = utf8.byte[0];
terminal->state = STATE_NORMAL;
if (isdigit(utf8.byte[0]) || isalpha(utf8.byte[0])) {
handle_special_escape(terminal, terminal->escape[1],
utf8.byte[0]);
continue;
}
} else if (terminal->state == STATE_ESCAPE_CSI) {
if (handle_special_char(terminal, utf8.byte[0]) != 0) {
/* do nothing */
} else if (utf8.byte[0] == '?') {
terminal->qmark_flag = 1;
} else {
/* Don't overflow the buffer */
if (terminal->escape_length < MAX_ESCAPE)
terminal->escape[terminal->escape_length++] = utf8.byte[0];
if (terminal->escape_length >= MAX_ESCAPE)
terminal->state = STATE_NORMAL;
}
if (isalpha(utf8.byte[0]) || utf8.byte[0] == '@' ||
utf8.byte[0] == '`')
{
terminal->state = STATE_NORMAL;
handle_escape(terminal);
continue;
} else {
continue;
} }
} }
/* this is valid, because ASCII characters are never used to
* introduce a multibyte sequence in UTF-8 */
if (utf8.byte[0] == '\e') {
terminal->state = STATE_ESCAPE;
terminal->escape[0] = '\e';
terminal->escape_length = 1;
terminal->qmark_flag = 0;
} else {
handle_char(terminal, utf8);
} /* if */
} /* for */
window_schedule_redraw(terminal->window); window_schedule_redraw(terminal->window);
} }

Loading…
Cancel
Save