import json import logging import re from typing import List, Pattern, Dict, Match from typing import Union, Any, TypeVar, Callable from .utils import _match_record from .utils import _match_value_to_rule as _match_pref logger = logging.getLogger("desktopenv.metric.thunderbird") V = TypeVar("Value") _pref_pattern: Pattern[str] = re.compile(r'^user_pref\("(?P(?:[^"]|\\")+)\", (?P.+)\);$'); def check_thunderbird_prefs(result: str, rule: Dict[str, Dict[str, Dict[str, Any]]]): """ Args: result (str): path to result file rule (Dict[str, Dict[str, Dict[str, Any]]]): dict like { "expect": { str: { "method": str "ref": something } } "unexpect": { str: { "method": str "ref": something } } } Returns: float """ if result is None: return 0. expect_rules = rule.get("expect", {}) unexpect_rules = rule.get("unexpect", {}) expect_metrics = {k: False for k in expect_rules} unexpect_metric = True with open(result) as f: for l in f: match_: Match[str] = _pref_pattern.match(l.strip()) if match_ is None: continue key: str = match_.group("key") # value: str = match_.group("val") # if value in {"true", "false"}: # value = value.title() # value: V = eval(value) value = json.loads(match_.group("val")) if key in expect_rules: logger.debug("K: %s, V: %s", key, repr(value)) expect_metrics[key] = _match_pref(value, expect_rules[key]) elif key in unexpect_rules: unexpect_metric = unexpect_metric and not _match_pref(value, unexpect_rules[key]) return float(all(expect_metrics.values()) and unexpect_metric) _value_processor: Callable[[str], str] = lambda val: val.replace("\\\"", "\"").replace("\\\\", "\\") # _condition_pattern: Pattern[str] = re.compile(r'(?PAND|OR) \((?P[\w ]+),(?P[\w ' + '\'' + r']+),(?:"(?P(?:[^"]|\")+)"|(?P[^)]+))\)') _condition_pattern: Pattern[str] = re.compile( r'\b(?:AND|OR) \((?:[\w ]+),(?:[\w ' + '\'' + r']+),(?:"(?:(?:[^"]|\")+)"|(?:[^)]+))\)|\bALL\b') def check_thunderbird_filter(result: str, rules: Dict[str, List[Dict[str, str]]]) -> float: """ Args: result (str): path to filter def file rules (Dict[str, List[Dict[str, str]]]): dict like { "expect": [{key: value}] "unexpect": [{key: value}] } Returns: float """ if result is None: return 0. # read filter def file # a filter: # { # "name": "Name", # "enabled": "yes" | "no", # "type": "17", # "action": "Move to folder" | ..., # "actionValue": ..., # "condition": [...] # } filters: List[Dict[str, Union[str, List[str]]]] = [] with open(result) as f: for l in f: if l.startswith("name="): filter_: Dict[str, Union[str, List[str]]] = {} filter_["name"] = _value_processor(l[6:-2]) elif l.startswith("enabled="): filter_["enabled"] = _value_processor(l[9:-2]) elif l.startswith("type="): filter_["type"] = _value_processor(l[6:-2]) elif l.startswith("action="): filter_["action"] = _value_processor(l[8:-2]) elif l.startswith("actionValue="): filter_["actionValue"] = _value_processor(l[13:-2]) elif l.startswith("condition="): condition_str: str = _value_processor(l[11:-2]) logger.debug("FILTER CONDITION: %s", condition_str) conditions: List[str] = \ _condition_pattern.findall(condition_str) logger.debug("FILTER CONDITIONS: %s", repr(conditions)) filter_["condition"] = conditions logger.debug("FILTER %s", repr(filter_)) filters.append(filter_) expect_metrics = [False] * len(rules.get("expect", [])) unexpect_metric = True for flt in filters: for i, r in enumerate(rules.get("expect", [])): expect_metrics[i] = expect_metrics[i] or _match_record(r, flt) unexpect_metric = unexpect_metric and not any(_match_record(r, flt) for r in rules.get("unexpect", [])) return float(all(expect_metrics) and unexpect_metric) def check_thunderbird_folder(result: Union[str, List[str]], reference: Union[str, List[str]], **kwargs) -> float: """ Check the file or file_list that each text file contains all messages in a folder in Thunderbird. Each message is started with `FROM - `. **kwargs: ignore_status (bool): for comparison, ignore the status (X-Mozilla-Status: 0000) of each message. default: False ignore_keys (bool): for comparison, ignore the keys (X-Mozilla-Keys: label) of each message. default: False remove_deleted (bool): ignore deleted messages which has status code 0008 or 0009. default: True remove_duplicate (bool): remove duplicate messages. default: True """ def normalize_msg(msg, options): ignore_status = options.get('ignore_status', False) ignore_keys = options.get('ignore_keys', False) if ignore_status: msg = re.sub(r'X-Mozilla-Status\d?:[\s\d]+', '', msg) if ignore_keys: msg = re.sub(r'(X-Mozilla-Keys:[^\n]*?)\n(MIME-Version)', r'\2', msg) return msg.strip() def read_thunderbird_folder_file(path: str) -> str: with open(path, 'r') as inf: data = inf.read().strip() messages = [] for mail in data.split('FROM - '): if mail.strip(): continue if kwargs.get('remove_deleted', True) and re.search(r'X-Mozilla-Status: 000[89]', mail): continue messages.append('FROM - ' + normalize_msg(mail, kwargs)) if kwargs.get('remove_duplicate', True): messages = set(messages) return '\n'.join(sorted(messages)) if type(reference) != list: result, reference = [result], [reference] for pred, gold in zip(result, reference): if pred is None: return .0 mail1 = read_thunderbird_folder_file(pred) mail2 = read_thunderbird_folder_file(gold) if mail1 != mail2: return .0 return 1.0