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:
|
try:
|
||||||
storage = Storage()
|
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("# Forensic Notes Export\n\n")
|
||||||
f.write(f"Generated on: {time.ctime()}\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():
|
if not self.data_file.exists():
|
||||||
return []
|
return []
|
||||||
try:
|
try:
|
||||||
with open(self.data_file, 'r') as f:
|
with open(self.data_file, 'r', encoding='utf-8') as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
return [Case.from_dict(c) for c in data]
|
return [Case.from_dict(c) for c in data]
|
||||||
except (json.JSONDecodeError, IOError):
|
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]
|
data = [c.to_dict() for c in self.cases]
|
||||||
# Write to temp file then rename for atomic-ish write
|
# Write to temp file then rename for atomic-ish write
|
||||||
temp_file = self.data_file.with_suffix(".tmp")
|
temp_file = self.data_file.with_suffix(".tmp")
|
||||||
with open(temp_file, 'w') as f:
|
with open(temp_file, 'w', encoding='utf-8') as f:
|
||||||
json.dump(data, f, indent=2)
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
temp_file.replace(self.data_file)
|
temp_file.replace(self.data_file)
|
||||||
|
|
||||||
def add_case(self, case: Case):
|
def add_case(self, case: Case):
|
||||||
@@ -225,15 +225,15 @@ class StateManager:
|
|||||||
state["evidence_id"] = evidence_id
|
state["evidence_id"] = evidence_id
|
||||||
# Atomic write: write to temp file then rename
|
# Atomic write: write to temp file then rename
|
||||||
temp_file = self.state_file.with_suffix(".tmp")
|
temp_file = self.state_file.with_suffix(".tmp")
|
||||||
with open(temp_file, 'w') as f:
|
with open(temp_file, 'w', encoding='utf-8') as f:
|
||||||
json.dump(state, f)
|
json.dump(state, f, ensure_ascii=False)
|
||||||
temp_file.replace(self.state_file)
|
temp_file.replace(self.state_file)
|
||||||
|
|
||||||
def get_active(self) -> dict:
|
def get_active(self) -> dict:
|
||||||
if not self.state_file.exists():
|
if not self.state_file.exists():
|
||||||
return {"case_id": None, "evidence_id": None}
|
return {"case_id": None, "evidence_id": None}
|
||||||
try:
|
try:
|
||||||
with open(self.state_file, 'r') as f:
|
with open(self.state_file, 'r', encoding='utf-8') as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except (json.JSONDecodeError, IOError):
|
except (json.JSONDecodeError, IOError):
|
||||||
return {"case_id": None, "evidence_id": None}
|
return {"case_id": None, "evidence_id": None}
|
||||||
@@ -242,7 +242,7 @@ class StateManager:
|
|||||||
if not self.settings_file.exists():
|
if not self.settings_file.exists():
|
||||||
return {"pgp_enabled": True}
|
return {"pgp_enabled": True}
|
||||||
try:
|
try:
|
||||||
with open(self.settings_file, 'r') as f:
|
with open(self.settings_file, 'r', encoding='utf-8') as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except (json.JSONDecodeError, IOError):
|
except (json.JSONDecodeError, IOError):
|
||||||
return {"pgp_enabled": True}
|
return {"pgp_enabled": True}
|
||||||
@@ -252,6 +252,6 @@ class StateManager:
|
|||||||
settings[key] = value
|
settings[key] = value
|
||||||
# Atomic write: write to temp file then rename
|
# Atomic write: write to temp file then rename
|
||||||
temp_file = self.settings_file.with_suffix(".tmp")
|
temp_file = self.settings_file.with_suffix(".tmp")
|
||||||
with open(temp_file, 'w') as f:
|
with open(temp_file, 'w', encoding='utf-8') as f:
|
||||||
json.dump(settings, f)
|
json.dump(settings, f, ensure_ascii=False)
|
||||||
temp_file.replace(self.settings_file)
|
temp_file.replace(self.settings_file)
|
||||||
|
|||||||
40
trace/tui.py
40
trace/tui.py
@@ -1898,7 +1898,7 @@ class TUI:
|
|||||||
line = lines[cursor_line]
|
line = lines[cursor_line]
|
||||||
lines[cursor_line] = line[:cursor_col] + chr(ch) + line[cursor_col:]
|
lines[cursor_line] = line[:cursor_col] + chr(ch) + line[cursor_col:]
|
||||||
cursor_col += 1
|
cursor_col += 1
|
||||||
|
|
||||||
# Auto-wrap to next line if cursor exceeds visible width
|
# Auto-wrap to next line if cursor exceeds visible width
|
||||||
if cursor_col >= input_width:
|
if cursor_col >= input_width:
|
||||||
# Always ensure there's a next line to move to
|
# Always ensure there's a next line to move to
|
||||||
@@ -1911,6 +1911,42 @@ class TUI:
|
|||||||
if cursor_line >= scroll_offset + input_height:
|
if cursor_line >= scroll_offset + input_height:
|
||||||
scroll_offset = cursor_line - input_height + 1
|
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):
|
def dialog_confirm(self, message):
|
||||||
curses.curs_set(0)
|
curses.curs_set(0)
|
||||||
h = 5
|
h = 5
|
||||||
@@ -2643,7 +2679,7 @@ class TUI:
|
|||||||
|
|
||||||
# Write to file
|
# Write to file
|
||||||
try:
|
try:
|
||||||
with open(filepath, 'w') as f:
|
with open(filepath, 'w', encoding='utf-8') as f:
|
||||||
f.write('\n'.join(lines))
|
f.write('\n'.join(lines))
|
||||||
self.show_message(f"IOCs exported to: {filepath}")
|
self.show_message(f"IOCs exported to: {filepath}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user