# -*- coding: utf-8 -*- """归档消息中短文本 protobuf 的保守解析器。""" from __future__ import annotations import hashlib import re from typing import Any _HEX_RE = re.compile(r"^[0-9a-fA-F]+$") _TEXT_TYPES = {"text", "文本"} _MAX_HEX_CHARS = 256 * 1024 def _read_varint(data: bytes, offset: int) -> tuple[int, int] | None: value = 0 shift = 0 for index in range(10): if offset >= len(data): return None current = data[offset] offset += 1 value |= (current & 0x7F) << shift if current < 0x80: return value, offset shift += 7 return None def _readable_utf8(data: bytes) -> str: if not data: return "" try: text = data.decode("utf-8") except UnicodeDecodeError: return "" if "\x00" in text or "\ufffd" in text: return "" if not all(character.isprintable() or character in "\r\n\t" for character in text): return "" return text.strip() def _protobuf_texts( data: bytes, *, depth: int = 0, max_depth: int = 8 ) -> list[tuple[int, str]]: if not data or depth > max_depth: return [] offset = 0 result: list[tuple[int, str]] = [] parsed_fields = 0 while offset < len(data): key_result = _read_varint(data, offset) if key_result is None: return [] key, offset = key_result field_number = key >> 3 wire_type = key & 7 if field_number <= 0: return [] parsed_fields += 1 if wire_type == 0: value_result = _read_varint(data, offset) if value_result is None: return [] _, offset = value_result elif wire_type == 1: offset += 8 elif wire_type == 2: length_result = _read_varint(data, offset) if length_result is None: return [] length, offset = length_result end = offset + length if length < 0 or end > len(data): return [] value = data[offset:end] offset = end readable = _readable_utf8(value) if readable: result.append((depth, readable)) result.extend(_protobuf_texts(value, depth=depth + 1, max_depth=max_depth)) elif wire_type == 5: offset += 4 else: return [] if offset > len(data): return [] return result if parsed_fields else [] def decode_hex_protobuf_text(value: Any, message_type: Any) -> str: """从十六进制 protobuf 中提取文本,只对明确的文本消息生效。""" if str(message_type or "").strip().casefold() not in _TEXT_TYPES: return "" raw = str(value or "").strip() if not raw or len(raw) > _MAX_HEX_CHARS: return "" decoded_parts: list[str] = [] for raw_part in raw.split("|"): part = re.sub(r"\s+", "", raw_part) if not part or len(part) % 2 or not _HEX_RE.fullmatch(part): return "" try: data = bytes.fromhex(part) except ValueError: return "" candidates = _protobuf_texts(data) if not candidates: return "" deepest = max(depth for depth, _ in candidates) texts = [text for depth, text in candidates if depth == deepest] for text in texts: if text and text not in decoded_parts: decoded_parts.append(text) return " | ".join(decoded_parts) def _protobuf_fields(data: bytes) -> dict[int, list[tuple[int, Any]]]: fields: dict[int, list[tuple[int, Any]]] = {} offset = 0 while offset < len(data): key_result = _read_varint(data, offset) if key_result is None: return {} key, offset = key_result field_number = key >> 3 wire_type = key & 7 if field_number <= 0: return {} if wire_type == 0: value_result = _read_varint(data, offset) if value_result is None: return {} value, offset = value_result elif wire_type == 1: end = offset + 8 if end > len(data): return {} value = data[offset:end] offset = end elif wire_type == 2: length_result = _read_varint(data, offset) if length_result is None: return {} length, offset = length_result end = offset + length if end > len(data): return {} value = data[offset:end] offset = end elif wire_type == 5: end = offset + 4 if end > len(data): return {} value = data[offset:end] offset = end else: return {} fields.setdefault(field_number, []).append((wire_type, value)) return fields def parse_file_message_metadata(value: Any, content_type: Any = None) -> dict[str, Any]: """解析企业微信 content_type=20 的文件卡片,不暴露远端令牌。""" try: if int(content_type) != 20: return {} except (TypeError, ValueError): return {} if isinstance(value, memoryview): value = value.tobytes() if not isinstance(value, bytes) or not value: return {} fields = _protobuf_fields(value) filename_values = fields.get(2) or [] size_values = fields.get(4) or [] checksum_values = fields.get(10) or [] if not filename_values or not size_values: return {} filename = _readable_utf8(filename_values[0][1]) filename = re.split(r"[\\/]", filename)[-1].strip()[:512] if not filename or "." not in filename or any(ord(char) < 32 for char in filename): return {} size_bytes = int(size_values[0][1]) if size_values[0][0] == 0 else 0 if size_bytes <= 0: return {} checksum = "" if checksum_values and checksum_values[0][0] == 2: checksum = _readable_utf8(checksum_values[0][1]).upper() if not re.fullmatch(r"[0-9A-F]{32,64}", checksum): checksum = "" remote_values = fields.get(1) or [] reference_hash = "" if remote_values and remote_values[0][0] == 2: reference_hash = hashlib.sha256(remote_values[0][1]).hexdigest() return { "original_filename": filename, "size_bytes": size_bytes, "checksum": checksum, "media_type": "file", "source_reference_sha256": reference_hash, } def file_message_content(metadata: dict[str, Any], *, cached: bool) -> str: filename = str(metadata.get("original_filename") or "未命名文件") size = int(metadata.get("size_bytes") or 0) if size >= 1024 * 1024 * 1024: size_label = f"{size / (1024 * 1024 * 1024):.2f} GB" elif size >= 1024 * 1024: size_label = f"{size / (1024 * 1024):.2f} MB" elif size >= 1024: size_label = f"{size / 1024:.1f} KB" else: size_label = f"{size} B" availability = "" if cached else ",源文件未缓存" return f"[文件] {filename}({size_label}{availability})" def _protobuf_text_field( fields: dict[int, list[tuple[int, Any]]], field_number: int ) -> str: values = fields.get(field_number) or [] if not values or values[0][0] != 2: return "" return _readable_utf8(values[0][1]) def parse_mini_program_metadata( value: Any, content_type: Any = None ) -> dict[str, Any]: """解析企业微信 content_type=78 的小程序卡片。""" try: if int(content_type) != 78: return {} except (TypeError, ValueError): return {} if isinstance(value, memoryview): value = value.tobytes() if not isinstance(value, bytes) or not value: return {} top_fields = _protobuf_fields(value) candidate: dict[int, list[tuple[int, Any]]] = {} for values in top_fields.values(): for wire_type, nested_value in values: if wire_type != 2 or not isinstance(nested_value, bytes): continue nested = _protobuf_fields(nested_value) app_id = _protobuf_text_field(nested, 2) page_path = _protobuf_text_field(nested, 3) if app_id.startswith("wx") and page_path: candidate = nested break if candidate: break if not candidate: return {} title = ( _protobuf_text_field(candidate, 7) or _protobuf_text_field(candidate, 8) or _protobuf_text_field(top_fields, 3) ) app_name = _protobuf_text_field(candidate, 10) page_path = _protobuf_text_field(candidate, 3) if not title or not page_path: return {} return { "title": title[:512], "app_name": app_name[:512], "app_id": _protobuf_text_field(candidate, 2)[:128], "username": _protobuf_text_field(candidate, 1)[:255], "page_path": page_path[:2048], "cover_url": ( _protobuf_text_field(candidate, 6) or _protobuf_text_field(top_fields, 2) )[:4096], } def mini_program_content(metadata: dict[str, Any]) -> str: lines = [f"[小程序] {str(metadata.get('title') or '未命名小程序')}"] if metadata.get("app_name"): lines.append(f"应用:{metadata['app_name']}") if metadata.get("app_id"): lines.append(f"AppID:{metadata['app_id']}") if metadata.get("page_path"): lines.append(f"页面:{metadata['page_path']}") return "\n".join(lines)