mirror of
https://gitlab.freedesktop.org/wayland/wayland.git
synced 2025-11-03 09:01:42 -05:00
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>
This commit is contained in:
parent
a0ee21c7dc
commit
b8609ada50
1 changed files with 157 additions and 58 deletions
|
|
@ -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,14 +716,99 @@ handle_sgr(struct terminal *terminal, int code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Returns 1 if c was special, otherwise 0 */
|
||||||
|
static int
|
||||||
|
handle_special_char(struct terminal *terminal, char c)
|
||||||
|
{
|
||||||
|
union utf8_char *row;
|
||||||
|
struct attr *attr_row;
|
||||||
|
|
||||||
|
row = terminal_get_row(terminal, terminal->row);
|
||||||
|
attr_row = terminal_get_attr_row(terminal, terminal->row);
|
||||||
|
|
||||||
|
switch(c) {
|
||||||
|
case '\r':
|
||||||
|
terminal->column = 0;
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
terminal->column = 0;
|
||||||
|
/* fallthrough */
|
||||||
|
case '\v':
|
||||||
|
case '\f':
|
||||||
|
if (terminal->row + 1 < terminal->height) {
|
||||||
|
terminal->row++;
|
||||||
|
} else {
|
||||||
|
terminal->start++;
|
||||||
|
if (terminal->start == terminal->height)
|
||||||
|
terminal->start = 0;
|
||||||
|
memset(terminal_get_row(terminal, terminal->row),
|
||||||
|
0, terminal->width * sizeof(union utf8_char));
|
||||||
|
attr_init(terminal_get_attr_row(terminal, terminal->row),
|
||||||
|
terminal->curr_attr, terminal->width);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
memset(&row[terminal->column], ' ', (-terminal->column & 7) * sizeof(union utf8_char));
|
||||||
|
attr_init(&attr_row[terminal->column], terminal->curr_attr, -terminal->column & 7);
|
||||||
|
terminal->column = (terminal->column + 7) & ~7;
|
||||||
|
break;
|
||||||
|
case '\b':
|
||||||
|
if (terminal->column > 0)
|
||||||
|
terminal->column--;
|
||||||
|
break;
|
||||||
|
case '\a':
|
||||||
|
/* Bell */
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
attr_row[terminal->column++] = terminal->curr_attr;
|
||||||
|
|
||||||
|
if (utf8.ch != terminal->last_char.ch)
|
||||||
|
terminal->last_char = utf8;
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
union utf8_char utf8;
|
union utf8_char utf8;
|
||||||
enum utf8_state parser_state;
|
enum utf8_state parser_state;
|
||||||
union utf8_char *row;
|
|
||||||
struct attr *attr_row;
|
|
||||||
|
|
||||||
for (i = 0; i < length; i++) {
|
for (i = 0; i < length; i++) {
|
||||||
parser_state =
|
parser_state =
|
||||||
|
|
@ -725,69 +828,65 @@ terminal_data(struct terminal *terminal, const char *data, size_t length)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
row = terminal_get_row(terminal, terminal->row);
|
/* assume escape codes never use non-ASCII characters */
|
||||||
attr_row = terminal_get_attr_row(terminal, terminal->row);
|
|
||||||
|
|
||||||
if (terminal->state == STATE_ESCAPE) {
|
if (terminal->state == STATE_ESCAPE) {
|
||||||
terminal->escape[terminal->escape_length++] = utf8.byte[0];
|
terminal->escape[terminal->escape_length++] = utf8.byte[0];
|
||||||
if (terminal->escape_length == 2 && utf8.byte[0] != '[') {
|
if (utf8.byte[0] == '[') {
|
||||||
/* Bad escape sequence. */
|
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;
|
terminal->state = STATE_NORMAL;
|
||||||
goto cancel_escape;
|
handle_non_csi_escape(terminal, utf8.byte[0]);
|
||||||
}
|
|
||||||
|
|
||||||
if (isalpha(utf8.byte[0])) {
|
|
||||||
terminal->state = STATE_NORMAL;
|
|
||||||
handle_escape(terminal);
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
} else if (terminal->state == STATE_ESCAPE_SPECIAL) {
|
||||||
cancel_escape:
|
terminal->escape[terminal->escape_length++] = utf8.byte[0];
|
||||||
switch (utf8.byte[0]) {
|
terminal->state = STATE_NORMAL;
|
||||||
case '\r':
|
if (isdigit(utf8.byte[0]) || isalpha(utf8.byte[0])) {
|
||||||
terminal->column = 0;
|
handle_special_escape(terminal, terminal->escape[1],
|
||||||
break;
|
utf8.byte[0]);
|
||||||
case '\n':
|
continue;
|
||||||
terminal->column = 0;
|
}
|
||||||
if (terminal->row + 1 < terminal->height) {
|
} else if (terminal->state == STATE_ESCAPE_CSI) {
|
||||||
terminal->row++;
|
if (handle_special_char(terminal, utf8.byte[0]) != 0) {
|
||||||
|
/* do nothing */
|
||||||
|
} else if (utf8.byte[0] == '?') {
|
||||||
|
terminal->qmark_flag = 1;
|
||||||
} else {
|
} else {
|
||||||
terminal->start++;
|
/* Don't overflow the buffer */
|
||||||
if (terminal->start == terminal->height)
|
if (terminal->escape_length < MAX_ESCAPE)
|
||||||
terminal->start = 0;
|
terminal->escape[terminal->escape_length++] = utf8.byte[0];
|
||||||
memset(terminal_get_row(terminal, terminal->row),
|
if (terminal->escape_length >= MAX_ESCAPE)
|
||||||
0, terminal->width * sizeof(union utf8_char));
|
terminal->state = STATE_NORMAL;
|
||||||
attr_init(terminal_get_attr_row(terminal, terminal->row),
|
|
||||||
terminal->curr_attr, terminal->width);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
if (isalpha(utf8.byte[0]) || utf8.byte[0] == '@' ||
|
||||||
case '\t':
|
utf8.byte[0] == '`')
|
||||||
memset(&row[terminal->column], ' ', (-terminal->column & 7) * sizeof(union utf8_char));
|
{
|
||||||
attr_init(&attr_row[terminal->column], terminal->curr_attr, -terminal->column & 7);
|
terminal->state = STATE_NORMAL;
|
||||||
terminal->column = (terminal->column + 7) & ~7;
|
handle_escape(terminal);
|
||||||
break;
|
continue;
|
||||||
case '\e':
|
} 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->state = STATE_ESCAPE;
|
||||||
terminal->escape[0] = '\e';
|
terminal->escape[0] = '\e';
|
||||||
terminal->escape_length = 1;
|
terminal->escape_length = 1;
|
||||||
break;
|
terminal->qmark_flag = 0;
|
||||||
case '\b':
|
} else {
|
||||||
if (terminal->column > 0)
|
handle_char(terminal, utf8);
|
||||||
terminal->column--;
|
} /* if */
|
||||||
break;
|
} /* for */
|
||||||
case '\a':
|
|
||||||
/* Bell */
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (terminal->column < terminal->width)
|
|
||||||
if (utf8.byte[0] < 32) utf8.byte[0] += 64;
|
|
||||||
row[terminal->column] = utf8;
|
|
||||||
attr_row[terminal->column++] = terminal->curr_attr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window_schedule_redraw(terminal->window);
|
window_schedule_redraw(terminal->window);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue