"""将用户选中的 .lnk / .url 解析为实际要存储的路径(目标程序或 URL)。""" import base64 import json import os import re import subprocess import sys def _looks_like_shell_lnk(path: str) -> bool: """Windows Shell Link 文件头为 4C 00 00 00(扩展名异常时仍可识别)。""" try: with open(path, "rb") as f: return f.read(4) == b"L\x00\x00\x00" except OSError: return False def _read_internet_shortcut_url(path: str) -> str | None: try: with open(path, "r", encoding="utf-8", errors="ignore") as f: for line in f: s = line.strip() if s[:4].upper() == "URL=": return s[4:].strip() except OSError: pass return None def _resolve_windows_lnk(lnk_path: str) -> str | None: if sys.platform != "win32": return None env = os.environ.copy() env["LNK_B64"] = base64.b64encode(os.path.normpath(lnk_path).encode("utf-8")).decode( "ascii" ) ps = ( "$p=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($env:LNK_B64));" "$sc=(New-Object -ComObject WScript.Shell).CreateShortcut($p);" "@{ t=[string]$sc.TargetPath; a=[string]$sc.Arguments }|ConvertTo-Json -Compress" ) try: r = subprocess.run( [ "powershell", "-NoProfile", "-Sta", "-Command", ps, ], capture_output=True, text=True, encoding="utf-8", errors="replace", env=env, timeout=15, creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0), ) raw = (r.stdout or "").strip().lstrip("\ufeff") if not raw: return None data = json.loads(raw) target = (data.get("t") or "").strip() args = (data.get("a") or "").strip() if not target: return None low = target.lower() if low.endswith("cmd.exe") or low.endswith("\\cmd.exe"): for m in re.finditer( r"([a-zA-Z]:[^\"'\s]+\.(?:bat|cmd))|\x22([a-zA-Z]:[^\"]+\.(?:bat|cmd))\x22", args, re.IGNORECASE, ): cand = (m.group(1) or m.group(2) or "").strip('"') if cand and os.path.isfile(cand): return os.path.normpath(cand) if os.path.isfile(target) or low.startswith("\\\\"): return os.path.normpath(target) return os.path.normpath(target) except Exception: return None def path_for_storage(local_path: str) -> str: """ 拖拽/选择文件时写入数据库的 path:.lnk/.url 解析为目标,其它保持原路径。 解析失败时退回本地路径。 """ if not local_path: return local_path local_path = os.path.normpath(local_path.strip()) ext = os.path.splitext(local_path)[1].lower() if ext == ".url": try: if os.path.isfile(local_path): url = _read_internet_shortcut_url(local_path) if url: return url except (OSError, ValueError): pass return local_path try: is_file = os.path.isfile(local_path) except (OSError, ValueError): is_file = False is_lnk = ext in (".lnk", ".ink") or ( is_file and _looks_like_shell_lnk(local_path) ) if is_lnk and is_file: resolved = _resolve_windows_lnk(local_path) if resolved: return resolved return local_path def item_name_from_sources(local_path: str, store_path: str) -> str: """列表显示名:仍用快捷方式/所选文件的文件名(无扩展名),与原来行为一致。""" base = os.path.splitext(os.path.basename(local_path))[0] return base or os.path.basename(store_path)