mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-07-18 08:26:15 +00:00
172 lines
5.2 KiB
Python
172 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Reject non-streaming GPX/KML route and track file reads."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
SOURCE_SUFFIXES = {".c", ".cc", ".cpp", ".cxx", ".h", ".hpp", ".hh"}
|
|
|
|
TRACK_CONTEXT_MARKERS = (
|
|
".gpx",
|
|
".kml",
|
|
"gpx",
|
|
"kml",
|
|
"route_storage",
|
|
"route_path",
|
|
"load_map_track_file",
|
|
"track_file",
|
|
"trackers",
|
|
"routes",
|
|
)
|
|
|
|
TRACK_PATH_HINTS = (
|
|
"modules/ui_shared/src/ui/screens/gps/",
|
|
"modules/ui_shared/src/ui/screens/tracker/",
|
|
"modules/ui_shared/include/ui/screens/gps/",
|
|
"modules/ui_shared/include/ui/screens/tracker/",
|
|
"platform/esp/arduino_common/src/platform_ui_route_storage.cpp",
|
|
"platform/esp/idf_common/src/platform_ui_route_storage.cpp",
|
|
"platform/linux/common/src/platform/ui/route_storage.cpp",
|
|
)
|
|
|
|
FORBIDDEN_LINE_PATTERNS = (
|
|
("arduino_read_string", re.compile(r"\.readString\s*\(")),
|
|
("istreambuf_slurp", re.compile(r"istreambuf_iterator\s*<")),
|
|
("rdbuf_slurp", re.compile(r"<<\s*[^;]+\.rdbuf\s*\(")),
|
|
)
|
|
|
|
SIZE_READ_PATTERN = re.compile(r"\b(?:size|available)\s*\(\s*\)")
|
|
READ_CALL_PATTERN = re.compile(r"\.read\s*\(")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Violation:
|
|
path: Path
|
|
line_number: int
|
|
rule: str
|
|
line: str
|
|
detail: str
|
|
|
|
|
|
def normalize(path: Path) -> str:
|
|
return path.relative_to(REPO_ROOT).as_posix()
|
|
|
|
|
|
def git_tracked_files() -> list[Path]:
|
|
try:
|
|
result = subprocess.run(
|
|
["git", "ls-files"],
|
|
cwd=REPO_ROOT,
|
|
check=True,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
)
|
|
except (OSError, subprocess.CalledProcessError):
|
|
return []
|
|
return [REPO_ROOT / line for line in result.stdout.splitlines() if line]
|
|
|
|
|
|
def walk_source_files() -> list[Path]:
|
|
tracked = git_tracked_files()
|
|
if tracked:
|
|
return [path for path in tracked if path.suffix.lower() in SOURCE_SUFFIXES]
|
|
|
|
source_files: list[Path] = []
|
|
excluded_dirs = {".git", ".pio", ".tmp", "build", "dist", "third_party"}
|
|
for root, dir_names, file_names in os.walk(REPO_ROOT):
|
|
dir_names[:] = [name for name in dir_names if name not in excluded_dirs]
|
|
root_path = Path(root)
|
|
for file_name in file_names:
|
|
path = root_path / file_name
|
|
if path.suffix.lower() in SOURCE_SUFFIXES:
|
|
source_files.append(path)
|
|
return source_files
|
|
|
|
|
|
def is_track_related(relative: str, text: str) -> bool:
|
|
if any(relative.startswith(prefix) for prefix in TRACK_PATH_HINTS):
|
|
return True
|
|
lowered = text.lower()
|
|
return any(marker in lowered for marker in TRACK_CONTEXT_MARKERS)
|
|
|
|
|
|
def local_track_context(lines: list[str], index: int, radius: int = 8) -> bool:
|
|
start = max(0, index - radius)
|
|
end = min(len(lines), index + radius + 1)
|
|
local = "\n".join(lines[start:end]).lower()
|
|
return any(marker in local for marker in TRACK_CONTEXT_MARKERS)
|
|
|
|
|
|
def collect_violations() -> list[Violation]:
|
|
violations: list[Violation] = []
|
|
for path in walk_source_files():
|
|
relative = normalize(path)
|
|
try:
|
|
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
except OSError:
|
|
continue
|
|
if not is_track_related(relative, text):
|
|
continue
|
|
|
|
lines = text.splitlines()
|
|
for line_number, line in enumerate(lines, start=1):
|
|
for rule, pattern in FORBIDDEN_LINE_PATTERNS:
|
|
if pattern.search(line) and local_track_context(lines, line_number - 1):
|
|
violations.append(
|
|
Violation(
|
|
path=path,
|
|
line_number=line_number,
|
|
rule=rule,
|
|
line=line.strip(),
|
|
detail="GPX/KML route and track files must be consumed as bounded streams.",
|
|
)
|
|
)
|
|
|
|
for index, line in enumerate(lines):
|
|
if not SIZE_READ_PATTERN.search(line):
|
|
continue
|
|
window = lines[index : min(index + 6, len(lines))]
|
|
if any(READ_CALL_PATTERN.search(candidate) for candidate in window) and local_track_context(
|
|
lines, index
|
|
):
|
|
violations.append(
|
|
Violation(
|
|
path=path,
|
|
line_number=index + 1,
|
|
rule="size_then_read",
|
|
line=line.strip(),
|
|
detail="Do not allocate/read a whole GPX/KML file from its size; use chunked streaming.",
|
|
)
|
|
)
|
|
return violations
|
|
|
|
|
|
def main() -> int:
|
|
violations = collect_violations()
|
|
if not violations:
|
|
print("Track file streaming check passed.")
|
|
return 0
|
|
|
|
print("Track file streaming check failed:")
|
|
for violation in violations:
|
|
print(
|
|
f"{normalize(violation.path)}:{violation.line_number}: "
|
|
f"{violation.rule}: {violation.detail}"
|
|
)
|
|
print(f" {violation.line}")
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|