Skip to content

Commit fe431fd

Browse files
committed
ANSI Escape codes for color are now supported by the output console
1 parent 8eb8d5c commit fe431fd

File tree

3 files changed

+515
-384
lines changed

3 files changed

+515
-384
lines changed

src/ControlledProgram.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use tokio::{
77
time::{Duration, *},
88
};
99
use tracing::info;
10+
11+
use crate::ansi_to_html::ansi_to_html;
1012
#[derive(Debug, Clone, Serialize, Deserialize)]
1113
pub enum SpecializedServerTypes {
1214
Minecraft,
@@ -196,15 +198,16 @@ impl ControlledProgramInstance {
196198
let mut has_more = true;
197199

198200
while has_more {
199-
let mut buf = [0u8; 10000];
201+
let mut buf = [0u8; 4096];
200202
let take = self.process.stdout.as_mut();
201-
let read = match timeout(Duration::from_millis(10), take.unwrap().read(&mut buf)).await {
202-
Ok(val) => val.unwrap(),
203-
Err(_) => 0,
204-
};
203+
let read =
204+
match timeout(Duration::from_millis(10), take.unwrap().read(&mut buf)).await {
205+
Ok(val) => val.unwrap(),
206+
Err(_) => 0,
207+
};
205208
if read > 0 && line < read {
206209
let new_str = String::from_utf8_lossy(&buf[0..read]);
207-
out2.push_str(&new_str);
210+
out2.push_str(ansi_to_html(&new_str).as_str());
208211
line = read;
209212
}
210213

src/ansi_to_html.rs

Lines changed: 81 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
use ansi_escapers::{interpreter::*, types::*};
22

3+
/// Escapes HTML special characters to prevent XSS attacks
4+
fn escape_html(s: &str) -> String {
5+
s.chars()
6+
.map(|c| match c {
7+
'&' => "&amp;".to_string(),
8+
'<' => "&lt;".to_string(),
9+
'>' => "&gt;".to_string(),
10+
'"' => "&quot;".to_string(),
11+
'\'' => "&#39;".to_string(),
12+
_ => c.to_string(),
13+
})
14+
.collect()
15+
}
16+
317
// ANSI color constants
418
const COLOR_BLACK: &str = "#000000";
519
const COLOR_RED: &str = "#FF0000";
@@ -96,27 +110,72 @@ fn get_html_style(codes: Vec<SgrAttribute>) -> String {
96110
}
97111

98112
pub fn ansi_to_html(inp: &str) -> String {
99-
let mut interpreter = AnsiParser::new(inp);
113+
// Pre-process input to preserve newlines before ANSI parsing
114+
let inp = inp.replace("\n", "\\n").replace("\r", "\\r");
115+
116+
let mut interpreter = AnsiParser::new(&inp);
100117
let parse_result = interpreter.parse_annotated();
101-
parse_result
102-
.spans
103-
.iter()
104-
.map(|span| -> String {
105-
if span.end > parse_result.text.len() {
106-
return "".to_string();
107-
}
108-
let mut res: String = String::new();
109-
res += format!(
110-
"<span style=\"{}\">{}</span>",
111-
get_html_style(span.codes.clone()),
112-
&parse_result.text[span.start..span.end]
113-
)
114-
.as_str();
115-
116-
res
117-
})
118-
.filter(|x| !x.is_empty())
119-
//.map(|x| x.clone())
120-
.collect::<Vec<String>>()
121-
.join("")
118+
119+
// If there are no spans, just return the escaped text
120+
if parse_result.spans.is_empty() {
121+
// Restore newlines in the output
122+
return escape_html(&parse_result.text)
123+
.replace("\\n", "<br>")
124+
.replace("\\r", "");
125+
}
126+
127+
// Create styled spans
128+
let mut styled_spans = Vec::new();
129+
for span in &parse_result.spans {
130+
if span.end > parse_result.text.len() {
131+
continue;
132+
}
133+
134+
let style = get_html_style(span.codes.clone());
135+
// Escape HTML and preserve newlines by converting to <br>
136+
let content = escape_html(&parse_result.text[span.start..span.end])
137+
.replace("\\n", "<br>")
138+
.replace("\\r", "");
139+
140+
styled_spans.push((
141+
span.start,
142+
span.end,
143+
format!("<span style=\"{}\">{}</span>", style, content),
144+
));
145+
}
146+
147+
// Sort spans by start position
148+
styled_spans.sort_by_key(|&(start, _, _)| start);
149+
150+
// Build the final output by replacing text with styled spans
151+
let mut result = String::new();
152+
let mut current_pos = 0;
153+
154+
for (start, end, styled_span) in styled_spans {
155+
// Add any text before this span
156+
if start > current_pos {
157+
result.push_str(
158+
&escape_html(&parse_result.text[current_pos..start])
159+
.replace("\\n", "<br>")
160+
.replace("\\r", ""),
161+
);
162+
}
163+
164+
// Add the styled span
165+
result.push_str(&styled_span);
166+
167+
// Update current position
168+
current_pos = end;
169+
}
170+
171+
// Add any remaining text after the last span
172+
if current_pos < parse_result.text.len() {
173+
result.push_str(
174+
&escape_html(&parse_result.text[current_pos..])
175+
.replace("\\n", "<br>")
176+
.replace("\\r", ""),
177+
);
178+
}
179+
180+
result
122181
}

0 commit comments

Comments
 (0)