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