From 8328b86e77704c11e60e53995c9c6488d16bc268 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Fri, 14 Feb 2025 17:04:32 +0000 Subject: [PATCH] pty: Move filtering escape characters to the PTY This is probably the place where this should live. We are now processing cursor positioning events inside the same line and we are filtering out any escape sequences that we don't know. It is a bit sad to lose the colors, but I don't have to have two places where we process this. At least this is going to be more robust and we can be sure to not forward anything malicious to the controlling terminal. Signed-off-by: Michael Tremer --- src/pakfire/log_file.c | 108 ++----------------------------- src/pakfire/pty.c | 144 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 145 insertions(+), 107 deletions(-) diff --git a/src/pakfire/log_file.c b/src/pakfire/log_file.c index 2733d9465..7a37a0754 100644 --- a/src/pakfire/log_file.c +++ b/src/pakfire/log_file.c @@ -188,104 +188,7 @@ int pakfire_log_file_close(struct pakfire_log_file* self) { return 0; } -#define pakfire_log_file_process_line(file, line, buffer) \ - __pakfire_log_file_process_line(file, line, sizeof(line), buffer) - -static int __pakfire_log_file_process_line(struct pakfire_log_file* self, - char* line, ssize_t length, const char* p) { - char* e = NULL; - int num; - - // The cursor position - int cursor = 0; - - while (*p) { - // Match the beginning of an ESC sequence - switch (*p) { - case '\x1B': - p++; - - // Match CSI sequences - switch (*p) { - case '[': - p++; - - // Parse the number - num = strtoul(p, &e, 10); - - // Continue from after the number - p = e; - - switch (*p) { - // Move the cursor to the left - case 'D': - cursor -= (num > 0) ? num : 1; - - // Don't go too far left - if (cursor < 0) - cursor = 0; - break; - - // Move the cursor to the right - case 'C': - cursor += (num > 0) ? num : 1; - - // Don't go too far right - if (cursor >= length) - cursor = length - 1; - break; - - // Move to a certain column - case 'G': - cursor = (num > 0) ? num - 1 : 0; - - // Don't go too far right - if (cursor >= length) - cursor = length - 1; - break; - } - - // Skip the final character - p++; - break; - } - break; - - // Backspace - case '\x08': - if (cursor > 0) { - cursor--; - - // Erase the previous character - line[cursor] = ' '; - } - p++; - break; - - // Carriage Return - case '\x0d': - // Erase everything up to the cursor - for (int i = 0; i < cursor; i++) - line[i] = '\0'; - - cursor = 0; - p++; - break; - - // Normal characters - default: - if (cursor < length - 1) - line[cursor++] = *p; - p++; - break; - } - } - - return 0; -} - int pakfire_log_file_write(struct pakfire_log_file* self, const char* buffer, ssize_t length) { - char line[MAX_LINE_LENGTH] = {}; int r; // Check inputs @@ -296,14 +199,13 @@ int pakfire_log_file_write(struct pakfire_log_file* self, const char* buffer, ss if (unlikely(!self->f)) return -EBADF; - // Process any ANSI Escape codes - r = pakfire_log_file_process_line(self, line, buffer); - if (r < 0) - return r; + // Automatically determine the length + if (unlikely(length < 0)) + length = strlen(buffer); // Write the buffer to the file - r = fprintf(self->f, "%s", line); - if (unlikely(r < 0)) { + r = fwrite(buffer, 1, length, self->f); + if (unlikely(r < length)) { ERROR(self->ctx, "Could not write to log file: %m\n"); return -errno; } diff --git a/src/pakfire/pty.c b/src/pakfire/pty.c index 30d6f26fe..852266768 100644 --- a/src/pakfire/pty.c +++ b/src/pakfire/pty.c @@ -36,13 +36,15 @@ #include #include +#define MAX_LINE_LENGTH 4096 + struct pakfire_pty_stdio { // File Descriptor int fd; unsigned close_fd:1; // Buffer - char buffer[4096]; + char buffer[MAX_LINE_LENGTH]; size_t buffered; // Terminal Attributes @@ -423,6 +425,141 @@ static int pakfire_pty_fill_buffer(struct pakfire_pty* pty, int fd, struct pakfi return bytes_read; } +#define pakfire_pty_sanitize_line(pty, line, buffer, buffer_length) \ + __pakfire_pty_sanitize_line(pty, line, sizeof(line), buffer, buffer_length) + +static int __pakfire_pty_sanitize_line(struct pakfire_pty* self, + char* line, ssize_t line_length, const char* buffer, ssize_t length) { + ssize_t i = 0; + int num; + + // The cursor position + int cursor = 0; + + while (i < length) { + switch (buffer[i]) { + // Escape + case '\x1b': + // Skip the escape character + i++; + + // CSI + if (i < length && buffer[i] == '[') { + i++; + + // Reset number + num = 0; + + // Parse the number + while (i < length) { + // Skip any ';' + if (buffer[i] == ';') { + i++; + continue; + + // Parse any digits + } else if (isdigit(buffer[i])) { + num = num * 10 + (buffer[i] - '0'); + i++; + continue; + } + + // Break on all other characters + break; + } + + // Parse the command + if (i < length) { + char command = buffer[i++]; + + switch (command) { + // Move the cursor to the left + case 'D': + cursor -= (num > 0) ? num : 1; + + // Don't go too far left + if (cursor < 0) + cursor = 0; + break; + + // Move the cursor to the right + case 'C': + cursor += (num > 0) ? num : 1; + + // Don't go too far right + if (cursor >= length) + cursor = length - 1; + break; + + // Move to a certain column + case 'G': + cursor = (num > 0) ? num - 1 : 0; + + // Don't go too far right + if (cursor >= length) + cursor = length - 1; + break; + + // Ignore any other sequences + default: + break; + } + } + } + break; + + // Backspace + case '\x08': + if (cursor > 0) { + cursor--; + + // Erase the previous character + line[cursor] = ' '; + } + i++; + break; + + // Carriage Return + case '\x0d': + // Erase everything up to the cursor + memset(line, '\0', cursor); + + cursor = 0; + i++; + break; + + // Normal characters + default: + if (cursor < line_length - 1) + line[cursor++] = buffer[i]; + i++; + break; + } + } + + return 0; +} + +static ssize_t pakfire_pty_send_line(struct pakfire_pty* self, + struct pakfire_pty_stdio* stdio, const char* buffer, ssize_t length) { + char line[MAX_LINE_LENGTH] = {}; + int r; + + // Sanitize the line + r = pakfire_pty_sanitize_line(self, line, buffer, length); + if (r < 0) + return r; + + // Call the callback + r = stdio->callbacks.stdout_callback(self->ctx, + stdio->callbacks.data, line, strlen(line)); + if (r < 0) + return r; + + // We have consumed the entire line + return length; +} + /* Writes as much data as possible from the buffer */ @@ -448,9 +585,8 @@ static int pakfire_pty_drain_buffer(struct pakfire_pty* pty, int fd, struct pakf eol = stdio->buffer + stdio->buffered - 1; } - // Call the callback - bytes_written = stdio->callbacks.stdout_callback( - pty->ctx, stdio->callbacks.data, stdio->buffer, eol - stdio->buffer + 1); + // Send the line + bytes_written = pakfire_pty_send_line(pty, stdio, stdio->buffer, eol - stdio->buffer + 1); // Abort on error if (bytes_written < 0) -- 2.39.5