mirror of
https://github.com/overcuriousity/trace.git
synced 2025-12-20 13:02:21 +00:00
Merge pull request #3 from overcuriousity/claude/fix-umlaut-support-01NVHwQkPasDs5ZPySfRqZgE
Fix UTF-8/umlaut support in note input
This commit is contained in:
@@ -69,7 +69,7 @@ def export_markdown(output_file: str = "export.md"):
|
||||
try:
|
||||
storage = Storage()
|
||||
|
||||
with open(output_file, "w") as f:
|
||||
with open(output_file, "w", encoding='utf-8') as f:
|
||||
f.write("# Forensic Notes Export\n\n")
|
||||
f.write(f"Generated on: {time.ctime()}\n\n")
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ Attachment: invoice.pdf.exe (double extension trick) #email-forensics #phishing-
|
||||
if not self.data_file.exists():
|
||||
return []
|
||||
try:
|
||||
with open(self.data_file, 'r') as f:
|
||||
with open(self.data_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return [Case.from_dict(c) for c in data]
|
||||
except (json.JSONDecodeError, IOError):
|
||||
@@ -176,8 +176,8 @@ Attachment: invoice.pdf.exe (double extension trick) #email-forensics #phishing-
|
||||
data = [c.to_dict() for c in self.cases]
|
||||
# Write to temp file then rename for atomic-ish write
|
||||
temp_file = self.data_file.with_suffix(".tmp")
|
||||
with open(temp_file, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
with open(temp_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
temp_file.replace(self.data_file)
|
||||
|
||||
def add_case(self, case: Case):
|
||||
@@ -225,15 +225,15 @@ class StateManager:
|
||||
state["evidence_id"] = evidence_id
|
||||
# Atomic write: write to temp file then rename
|
||||
temp_file = self.state_file.with_suffix(".tmp")
|
||||
with open(temp_file, 'w') as f:
|
||||
json.dump(state, f)
|
||||
with open(temp_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(state, f, ensure_ascii=False)
|
||||
temp_file.replace(self.state_file)
|
||||
|
||||
def get_active(self) -> dict:
|
||||
if not self.state_file.exists():
|
||||
return {"case_id": None, "evidence_id": None}
|
||||
try:
|
||||
with open(self.state_file, 'r') as f:
|
||||
with open(self.state_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, IOError):
|
||||
return {"case_id": None, "evidence_id": None}
|
||||
@@ -242,7 +242,7 @@ class StateManager:
|
||||
if not self.settings_file.exists():
|
||||
return {"pgp_enabled": True}
|
||||
try:
|
||||
with open(self.settings_file, 'r') as f:
|
||||
with open(self.settings_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, IOError):
|
||||
return {"pgp_enabled": True}
|
||||
@@ -252,6 +252,6 @@ class StateManager:
|
||||
settings[key] = value
|
||||
# Atomic write: write to temp file then rename
|
||||
temp_file = self.settings_file.with_suffix(".tmp")
|
||||
with open(temp_file, 'w') as f:
|
||||
json.dump(settings, f)
|
||||
with open(temp_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(settings, f, ensure_ascii=False)
|
||||
temp_file.replace(self.settings_file)
|
||||
|
||||
38
trace/tui.py
38
trace/tui.py
@@ -1911,6 +1911,42 @@ class TUI:
|
||||
if cursor_line >= scroll_offset + input_height:
|
||||
scroll_offset = cursor_line - input_height + 1
|
||||
|
||||
elif ch > 127:
|
||||
# UTF-8 multi-byte character (umlauts, etc.)
|
||||
# curses returns the first byte, we need to read the rest
|
||||
try:
|
||||
# Try to decode as UTF-8
|
||||
# For multibyte UTF-8, we need to collect all bytes
|
||||
bytes_collected = [ch]
|
||||
|
||||
# Determine how many bytes we need based on the first byte
|
||||
if ch >= 0xF0: # 4-byte character
|
||||
num_bytes = 4
|
||||
elif ch >= 0xE0: # 3-byte character
|
||||
num_bytes = 3
|
||||
elif ch >= 0xC0: # 2-byte character
|
||||
num_bytes = 2
|
||||
else:
|
||||
num_bytes = 1
|
||||
|
||||
# Read remaining bytes
|
||||
for _ in range(num_bytes - 1):
|
||||
next_ch = win.getch()
|
||||
bytes_collected.append(next_ch)
|
||||
|
||||
# Convert to character
|
||||
char_bytes = bytes([b & 0xFF for b in bytes_collected])
|
||||
char = char_bytes.decode('utf-8')
|
||||
|
||||
# Insert character at cursor
|
||||
line = lines[cursor_line]
|
||||
lines[cursor_line] = line[:cursor_col] + char + line[cursor_col:]
|
||||
cursor_col += 1
|
||||
|
||||
except (UnicodeDecodeError, ValueError):
|
||||
# If decode fails, ignore the character
|
||||
pass
|
||||
|
||||
def dialog_confirm(self, message):
|
||||
curses.curs_set(0)
|
||||
h = 5
|
||||
@@ -2643,7 +2679,7 @@ class TUI:
|
||||
|
||||
# Write to file
|
||||
try:
|
||||
with open(filepath, 'w') as f:
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(lines))
|
||||
self.show_message(f"IOCs exported to: {filepath}")
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user